2016年8月23日 By mikel 分类: ASP.NET MVC, JavaScript

来源: 如何在 ASP.NET MVC 中集成 AngularJS(1) – 葡萄城控件技术团队 – 博客园

介绍

当涉及到计算机软件的开发时,我想运用所有的最新技术。例如,前端使用最新的 JavaScript 技术,服务器端使用最新的基于 REST 的 Web API 服务。另外,还有最新的数据库技术、最新的设计模式和技术。

当选择最新的软件技术 时,有几个因素在起作用,其中包括如何将这些技术整合起来。过去两年中,我最喜欢的一项技术就是设计单页面应用(SPA)的 AngularJS。作为一个微软stack开发者,我也是使用 ASP.NET MVC 平台实现 MVC 设计模式和并进行研究的粉丝,包括它的捆绑和压缩功能以及实现其对 RESTful 服务的 Web API 控制器。

为了兼得两者,本文介绍了在 ASP.NET MVC 中集成 AngularJS 的两全其美的方案。

由于本文篇幅较长,故会分为3篇,分别进行介绍。

概述

本文中示例的 Web 应用程序将有三个目标:

  • 在前端页面中实现 AngularJS 和 JavaScript AngularJS 控制器
  • 使用微软的 ASP.NET MVC 平台来建立、引导并捆绑一个应用
  • 根据功能模型的需求,动态的加载 AngularJS 的控制器和服务

本文的示例应用程序将包含三个主要文件夹:关于联系和索引的主文件夹、允许你创建,更新和查询客户的客户文件夹、允许你创建,更新和查询产品的产品文件夹。

除了使用 AngularJS 和 ASP.NET MVC,这个应用程序也将实现使用微软的 ASP.NET Web API 服务来创建 RESTful 服务。微软的实体框架将用于生成并更新一个 SQL Server Express 数据库。

此应用程序也将用到一些使用 Ninject 的依赖注入。此外,也会运用流畅的界面和 lambda 表达式,来合并使用称为 FluentValidation的.NET 的小型验证库,用于构建驻留在应用业务层的验证业务规则。

AngularJS VS ASP.NET Razor 视图

几年来,我一直在使用 完整的 Microsoft ASP.NET MVC 平台来开发 Web 应用程序。相比于使用传统的 ASP.NET Web 窗体的 postback 模型, ASP.NET MVC 平台使用的是 Razor 视图。 这带来的是:适当的业务逻辑、数据和表示逻辑之间关注点的分离。在使用它的约定优于配置和简洁的设计模式进行 MVC 开发之后,你将永远不会想回过头去做 Web 窗体的开发。

ASP.NET MVC 平台及其 Razor 视图引擎,不但比 Web 窗体简洁,还鼓励和允许你将 .NET 服务器端代码和样式混合。在 Razor 视图中的 HTML 混合的 .NET 代码看起来像套管代码。另外,在 ASP.NET MVC 模式下,一些业务逻辑是可以被最终写入在 MVC 的控制器中。在MVC控制器中,写入代码来控制表示层中的信息,这是很有诱惑力的。

AngularJS 提供了以下对微软 ASP.NET MVC Razor 视图的增强功能:

  • AngularJS 视图是纯 HTML 的
  • AngularJS 视图被缓存在客户端上以实现更快的响应,并在每次请求不产生服务器端响应
  • AngularJS 提供了一个完整的框架,编写高质量的客户端 JavaScript 代码
  • AngularJS 提供了 JavaScript 控制器和 HTML 视图之间的完全分离

 

ASP.NET MVC 捆绑和压缩

捆绑和压缩是两种你可 以用来缩短 Web 应用程序的请求负载时间的技术。这是通过减少对服务器的请求数量和减小请求规模,来实现缩短请求负载时间的(如 CSS 和 JavaScript)。压缩技术通过复杂的代码逻辑也使得别人更难的侵入你的 JavaScript 代码。

当涉及到捆绑技术和 AngularJS 框架时,你会发现捆绑和压缩过程中会自动使用 Grunt 和 Gulp 之类的框架,Grunt 和 Gulp 技术是一种流行的 web 库并配有插件,它允许你自动化你的每一项工作。

如果你是一个微软开发 者,你可以使用它们在 Visual Studio 中一键式发布你的 Web 应用,而不用学习使用任何第三发工具和库类。幸运的是,捆绑和压缩是 ASP.NET 4.5 ASP.NET 中的一项功能,可以很容易地将多个文件合并或捆绑到一个文件中。你可以创建 CSS,JavaScript 和其他包。较少的文件意味着更少的 HTTP 请求,这也可以提高第一个页面的加载性能。

使用 RequireJS 来实现 MVC 捆绑的动态加载

在开发 AngularJS 单页的应用程序时,其中有一件事情是不确定的。由于应用开始时会被引导和下载,所以在主页面索引时,AngularJS 会请求所有的 JavaScript 文件和控制器。对于可能包含数百个 JavaScript 文件的大规模应用,这可能不是很理想。因为我想使用 ASP.NET 的捆绑来加载所有的 AngularJS 控制器。一旦开始索引,一个 ASP.NET 捆绑中的巨大的挑战将会出现在服务器端。

为了实现示例程序动态 地绑定 ASP.NET 文件包,我决定用 RequireJS JavaScript 库。RequireJS 是一个众所周知的 JavaScript 模块和文件加载器,最新版本的浏览器是支持 RequireJS 的。起初,这似乎是一个很简单的事情,但随着时间的推移,我完成了大量的代码的编写,却并没有解决使用服务器端 rendered bundle 与客户端 AngularJS 等技术的问题。

最终,在大量的研究和反复试验和失败后,我想出了少量代码却行之有效的解决方案。

本文的接下来部分将会展示,在 ASP.NET MVC 中集成 AngularJS 的过程。

创建 MVC 项目并安装 Angular NuGet 包

为了开始示例应用程 序,我通过在 Visual Studio 2013 专业版中选择 ASP.NET Web 应用程序模板来创建一个 ASP.NET MVC 5 Web 应用程序。之后,我选择了 MVC 工程并在应用中会用到 MVC Web API 添加文件夹和引用。下一步是选择工具菜单中的“管理 NuGet 包的解决方案”,来下载并安装 NuGet AngularJS。

对于此示例应用程序,我安装了所有的以下的 NuGet 包:

  • AngularJS – 安装整个 AngularJS 库
  • AngularJS UI – AngularJS 框架的伙伴套件UI工具和脚本。
  • AngularJS UI引导 – 包含一组原生 AngularJS 指令的引导标记和CSS
  • AngularJS 块UI – AngularJS BlockUI 指令,块状化 HTTP 中的请求
  • RequireJS – RequireJS 是一个 JavaScript 文件和模块加载
  • Ninject – 提供了支持 MVC 和 MVC Web API 支持的依赖注入
  • 实体框架 – 微软推荐的数据访问技术的新应用
  • 流畅的验证 – 建立验证规则的 .NET 验证库。
  • 优美字体- CSS 可立即定制的可升级的矢量图标

NuGet 是一个很好的包管理器。当你使用 NuGet 安装一个软件包,它会拷贝库文件到你的解决方案,并自动更新项目中的引用和配置文件。如果你删除一个包, NuGet 会让所有删除过程不会留下任何痕迹。

优美的URLS

对于此示例应用程序,我想在浏览器的地址栏中实现优美的网址。默认情况下,AngularJS 会将 URL 用#标签进行路由:

例如:

  • http://localhost:16390/
  • http://localhost:16390/#/contact
  • http://localhost:16390/#/about
  • http://localhost:16390/#/customers/CustomerInquiry
  • http://localhost:16390/#/products/ProductInquiry

通过转 向 html5Mode 和设置基本的 URL,可以很方便的清除 URLS 并去除 URL 中的#。在 HTML5 模式下,AngularJS 的$位置服务会和使用 HTML5 History API 的浏览器 URL 地址进行交互。HTML5 History API 是通过脚本来操作浏览器历史记录的标准方法,以这点为核心,是实现单页面应用的重点。

要打开 html5Mode,你需要在 Angular 的配置过程中,将 $locationProviderhtml5Mode 设置为 true,如下所示:

// CodeProjectRouting-production.js
angular.module("codeProject").config('$locationProvider', function ($locationProvider) {
    $locationProvider.html5Mode(true);
}]);

当你使用 html5Mode 配置 $locationProvider 时,你需要使用 href 标记来指定应用的基本 URL。基本 URL 用于在整个应用程序中,解决所有相对 URL 的问题。你可以在应用程序中设置,如下所示的母版页的 header 部分的基本 URL:

<!-- _Layout.cshtml -->
<html>
<head>
<basehref="http://localhost:16390/"/>
</head>

对于示例应用程序,我 以程序设置的方式将基本 URL 存储在 Web 配置文件中。这是一种最好的方式使得基本 URL 成为一种配置,这样能够让你根据环境、配置或者你开发的应用的站点的情况,来将基本 URL 设定为不同的值。此外,设置基本 URL 时,要确保基本 URL 以“/”为结尾,因为基本 URL 将是所有地址的前缀。

<!-- web.config.cs -->
<appsettings>
<addkey="BaseUrl"value="http://localhost:16390/"/>
</appsettings>

打开 html5Mode 并设置基本 URL 后,你需要以以下优美的 URL 作为结束:

  • http://localhost:16390/
  • http://localhost:16390/contact
  • http://localhost:16390/about
  • http://localhost:16390/customers/CustomerInquiry
  • http://localhost:16390/products/ProductInquiry

 

目录结构与配置

按照惯例,一个 MVC 项目模板要求所有的 Razor 视图驻留在视图文件夹中; 所有的 JavaScript 文件驻留在脚本文件夹; 所有的内容文件驻留在内容文件夹中。对于此示例应用程序,我想将所有的 Angular 视图和相关的 Angular JavaScript 控制器放入相同的目录下。基于 Web 的应用程序会变得非常大,我不想相关功能以整个应用程序的目录结构存储在不同文件夹中。

在示例应用程序,会出现两个 Razor 视图被用到,Index.cshtml 和 _Layout.cshtml 母版页布局,这两个 Razor 视图将用于引导和配置应用程序。应用程序的其余部分将包括 AngularJS 视图和控制器。

对于示例应用程序,我在视图文件夹下创建了两个额外的文件夹,一个客户的子文件夹,一个产品的子文件夹。所有的客户的 Angular 视图和控件器将驻留在客户子文件夹中,所有的产品的 Angular 视图和控件器将驻留在产品子文件夹中 。

由于 Angular 视图是 HTML 文件,而 Angular 控制器是 JavaScript 文件,从 Views 文件夹到浏览器,ASP.NET MVC 必须被配置为允许 HTML 文件和 JavaScript文 件进行访问和传递。这是一个 ASP.NET MVC 默认的约定。幸运的是,你可以通过编辑视图文件下的 web.config 文件并添加一个 HTML 和 JavaScript 的处理器来更改此约定,这将会使这些文件类型能够被送达至浏览器进行解析。

复制代码
<!-- web.config under the Views folder -->
<system.webserver>
<handlers>
<addname="JavaScriptHandler"path="*.js"verb="*"precondition="integratedMode"
type="System.Web.StaticFileHandler"/>

<addname="HtmlScriptHandler"path="*.html"verb="*"precondition="integratedMode"
type="System.Web.StaticFileHandler"/>
</handlers>
</system.webserver>
复制代码

应用程序版本自动刷新和工程构建

对于此示例应用程序, 我想跟踪每一次编译的版本和内部版本号,在属性文件夹下使用 AssemblyInfo.cs 文件的信息测试并发布这个应用。每次应用程序运行的时候, 我想获得最新版本的应用程序和使用的版本号,以实现最新的 HTML 文件和 JavaScript 文件生成时,帮助浏览器从缓存中,获取最新的文件来替换那些旧文件。

对于这种应用,我使用的 Visual Studio 2013 专业版,这让一切变得简单,我为 Visual Studio2013 专业版下载了一个自动版本的插件

https://visualstudiogallery.msdn.microsoft.com/dd8c5682-58a4-4c13-a0b4-9eadaba919fe

它会自动刷新 C# 和 VB.NET 项目的版本。将安装插件下载到名为自动版本设置的工具菜单中。该插件自带了配置工具,它允许你配置主要和次要版本号,以便每次编译时,自动的更新 AssemblyInfo.cs 文件。目前,这个插件只是在 Visual Studio 2013 专业版中支持,或者你也可以手动更新版本号或使用类似微软的 TFS 以持续构建和配置管理环境的方式,来管理你的版本号。

下面是一个使用更新的 AssemblyVersion 和 AssemlyFileVersion 号的示例,这个示例在版本编译之后会通过插件自动地进行更新。

复制代码
// AssemblyInfo.cs
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

[assembly: AssemblyTitle("CodeProject.Portal")]
[assembly: AssemblyProduct("CodeProject.Portal")]
[assembly: AssemblyCopyright("Copyright &copy; 2015")]

// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("1d9cf973-f876-4adb-82cc-ac4bdf5fc3bd")]
// Version information for an assembly consists of the following four values:

// Major Version
// Minor Version
// Build Number
// Revision

// You can specify all the values or you can default the Revision and Build Numbers
// by using the '*' as shown below:

[assembly: AssemblyVersion("2015.9.12.403")]
复制代码

使用 Angular 视图和控制器更换联系我们和关于 Razor 视图

要想使用 MVC 工程,首先要做的事情之一就是使用 AngularJS 视图和控制器来更换联系我们和关于 Razor 视图。这是一个很好的起点来测试你的配置是否能够使 AngularJS 正常建立并运行。随后如果不需要这些页面,你可以删除关于和联系我们的视图和控制器。

AngularJS 的这种创建控制器的方式是通过注入 $scope 实现的。示例应用程序的视图和控制器使用“controller as”语法。此语法并非使用控制器中的 $scope,而是简化你的控制器的语法。当你声明一个“controller as”语法的控制器时,你会得到该控制器的一个实例。

使用“controller as”语法,你的所有的连接到控制器(视图模式)的属性必须以你视图的别名作为前缀。在下面的视图代码片段,属性标题前面就加上了“VM”的别名。

<!-- aboutController.js -->
<div ng-controller="aboutController as vm" ng-init="vm.initializeController()">
   <h4 class="page-header">{{vm.title}}</h4>
</div>

当控制器构造函数被调 用时,使用“controller as”的语法,叫做“this”的控制器示例就会被创建。不需要使用 Angular 提供的 $scope 变量,你只需要简单的声明一个 vm 变量并分配“this”给它。所有被分配给 vm 对象的变量都会替换掉 $scope。有了分配给控制器功能的示例的变量,我们就可以使用这些别名并访问这些变量。

此外,所有示例应用程序中的控制器都是使用 “use strict”JavaScript 命令以一种严格的模式运行的。这种严格模式可以更容易地编写“安全”的 JavaScript 代码。严格模式将此前“不严格的语法”变成了真正的错误。作为一个例子,在一般的 JavaScript 中,错误输入变量名称会创建一个新的全局变量。在严格模式下,这将抛出一个错误,因此无法意外创建一个全局变量。

复制代码
// aboutController.js
angular.module("codeProject").register.controller('aboutController', 
['$routeParams', '$location', function ($routeParams, $location) {
{
    "use strict";
    var vm = this;

    this.initializeController = function () {
        vm.title = "About Us";
    }
}]);
复制代码

如前所述, 在 MVC Razor 视图中使用 AngularJS 视图和控制器的优势之一,就是 Angular 提供了很好的机制来编写高质量的 JavaScript 模块、一种纯 HTML 视图和 JavaScript 控制器之间的完全分离的编码方式。你不再需要使用 AngularJS 双向数据绑定技术来解析浏览器的文件对象模型,这也就使得你能够编写单元测试的 JavaScript 代码。

作为一个注脚,您将在 aboutController 看到一个名为 register.controller 的方法在本文的后面,你会看到注册方法是从哪儿来的和它用来做什么。

主页索引的 Razor 视图和 MVC 路由

ASP.NET MVC 中集成 AngularJS 的一件有趣的事情,就是应用程序实际上是如何启动和实现路由的。当你启动应用程序时,ASP.NET MVC 将会以如下默认的方式进入并查看路由表:

复制代码
// RouteConfig.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;

namespace CodeProject.Portal
{
    publicclass RouteConfig
    {
        publicstaticvoid RegisterRoutes(RouteCollection routes)
        {
          routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
          routes.MapRoute(
          name: "Default",
          url: "{controller}/{action}/{id}",
          defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
          );
        }
    }
}
复制代码

应用开始时,以上外装配置的 MVC 路由表中的配置,会将应用路由到 MVC Home 主控制器,并执行主控制器中的索引方法。这样会以 MVC 默认工程模板的形式,将 Index.cshtml MVC Razor 视图传递到用户输出的主页面内容中。

这个应用程序的目标是使用 Angular 视图取代所有的 MVC 视图。但问题是,甚至在 AngularJS 被启动之前,主页的 Razor 视图索引就已经被执行和注入了 _Layout.cshtml 主页面中。

自从我决定,将主页面改为 AngularJS 视图,我就使用包含 AngularJS ng-view 标签的 div 标签删除了索引 Razor 视图的所有内容。

<!-- Index.cshtml -->
<divng-view></div>

该 AngularJS ngView 标签是一个指令,能以一种将当前路由的模板渲染成主页面布局的方式补充 $route service。我有两个选择,要么直接嵌入 NG-View 代码到母版页 _Layout.cshtml 或使用 Razor 视图将它注入到母版页。我决定简单地从索引 Razor 视图中注入标签。本质上,索引 Razor 视图在应用程序的引导过程中被简单的使用,并且在应用程序启动后不会被引用。

一旦应用程序被引导并开始启动,AngularJS 将会执行自己的路由系统并以路由表中配置来执行自己的默认路由。基于这一点,我创建了一个单独 AngularJS index.html 和主页的 IndexController.js 文件。

<!-- Index.html -->
<divng-controller="indexController as vm"ng-init="vm.initializeController()">
<h4class="page-header">{{vm.title}}</h4>
</div>

当视图加载时,索引 Angular 视图将会通过 ng-init 指令来执行索引控制器的初始化功能。

复制代码
// indexController.js
angular.module("codeProject").register.controller('indexController',
['$routeParams', '$location', function ($routeParams, $location) {
"use strict";
var vm = this;
this.initializeController = function () {
        vm.title = "Home Page";
    }
}]);
复制代码

RouteConfig.cs

当开发一个 AngularJS 应用时,首先将会发生的一件事,就是你需要先开发一个像驻留在路由文件中的 CustomerInquiry 一样的页面

/Views/Customers/ CustomerInquiry

当你在 HTML 页面寻找这个视图时,点击 Visual Studio 中的运行按钮来直接执行这个页面,MVC 将会执行并尝试去查找一个用于客户路由的 MVC 控制器和视图。将会发生的是,你会获得一个叫做找不到该路由的视图或控制器的错误。

你当然会遇到这个错 误,因为/View/Customers/CustomerInquiry的路由是个 Angular 路由,而不是 MVC 路由。MVC 并不知道这个路由。如果你还想直接运行这个页面,则需要解决这一问题,给 MVC 路由表增加另外的路由以便告诉 MVC 将所有的请求路由到 MVC 主控制器,并渲染Razor 视图、通过路由引导这个应用。

由于我有三个视图文件 夹,主文件夹、客户文件夹和产品文件夹,我增加了一下的 MVC 路由配置类以便将所有的请求路由到主/索引路由中。当应用程序运行时点击 F5,同样也会进入 MVC 路由表。就 Angular 和单页面如何运行而言,当你点击 F5 时,基本上就是重启了 AngularJS 应用。

有了这些额外的路由,现在就可以直接执行 AngularJS 路由了。你可以在 MVC 路由表中以一种通配符的路由来处理你的路由,但我更愿意使用明确的路由表,并使得 MVC 拒绝所有无效的路由。

要记住的基本的事情是,MVC 路由将会在 AngularJS 启动之前发生,一旦引导开始,AngularJS 将会接管所有以后路由请求。

复制代码
// RouteConfig.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;

namespace CodeProject.Portal 
{
    publicclass RouteConfig
    {
        publicstaticvoid RegisterRoutes(RouteCollection routes)
        {

             routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
             routes.MapRoute(
             name: "HomeCatchAllRoute",
             url: "Home/{*.}",
             defaults: new { controller = "Home", action = "Index", 
             id = UrlParameter.Optional }
             );
             routes.MapRoute(
             name: "CustomersCatchAllRoute",
             url: "Customers/{*.}",
             defaults: new { controller = "Home", action = "Index", 
             id = UrlParameter.Optional }
             );

             routes.MapRoute(
             name: "ProductsCatchAllRoute",
             url: "Products/{*.}",
             defaults: new { controller = "Home", action = "Index", 
             id = UrlParameter.Optional }
             );

             routes.MapRoute(
             name: "Default",
             url: "{controller}/{action}/{id}",
             defaults: new { controller = "Home", action = "Index", 
             id = UrlParameter.Optional }
          );
       }
   }
}
复制代码

$ controllerProvider 和动态加载控制器

当示例应用程序启动时,该应用程序将会预加载应用程序的核心控制器和服务。这包括 Home 目录中的所有控制器和应用程序的共享服务。

此应用程序的共享服务,将在所有模块中执行- 包括一个 Ajax 服务和提醒服务。如前所述,此应用程序具有三个功能模块:基本的关于、联系我们和主页的模块、一个客户模块和产品模块。

由于此应用程序可随时间而增长,我不希望该在应用程序的配置和引导阶段中,预加载所有的功能模块。应用程序启动后,我仅希望当用户请求时,再加载这些控制器和产品模块。

默认情况下,AngularJS 被设计为预加载所有的控制器。一个典型的控制器看起来这样:

复制代码
// aboutController.js
angular.module("codeProject").controller('aboutController',
['$routeParams', '$location', function ($routeParams, $location) {
"use strict";
var vm = this;
this.initializeController = function () {
        vm.title = "About";
    }
}]);
复制代码

如果在配置阶段之后, 你尝试动态加载上述控制器,将会收到一个 Angular 错误。你需要做的是使用 $controllerProvider 服务器在配置阶段之后,动态地加载控制器。Angular 使用 $controllerProvider 服务来创建新的控制器。这种方法允许通过注册方法来实现控制器注册。

复制代码
// aboutController.js
angular.module("codeProject").register.controller('aboutController',
['$routeParams', '$location', function ($routeParams, $location) {
"use strict";
var vm = this;
this.initializeController = function () {
         vm.title = "About";
     }
}]);
复制代码

上述有关控制器被修改为执行 $controllerProvider 的寄存器方法。为了使这种注册方法有效,必须在配置阶段配置这种注册。下面的代码片段在应用程序启动之后,使用了 $controllerProvider 来使注册方法有效。在下面的例子中,提供了一种用于注册和动态加载两个控制器和服务的注册方法。如果你愿意,也可以包括 Angular 全部库和指令的注册功能。

复制代码
// CodeProjectBootStrap.js
(function () {
var app = angular.module('codeProject', ['ngRoute', 'ui.bootstrap', 'ngSanitize', 'blockUI']);

app.config(['$controllerProvider', '$provide', function ($controllerProvider, $provide) {
        app.register =
        {
             controller: $controllerProvider.register, 
             service: $provide.service
        }
    }
}
复制代码

以上是如何在 ASP.NET MVC 中集成 AngularJS 的第一部分内容,后续内容会在本系列的后两篇文章中呈现,敬请期待!

2016年8月23日 By mikel 分类: 数据库

UNIX时间戳转换为日期用函数FROM_UNIXTIME(),日期转换为UNIX时间戳用函数UNIX_TIMESTAMP()

来源: mysql UNIX时间戳与日期的相互转换 – 站长之家

UNIX时间戳转换为日期用函数: FROM_UNIXTIME()

select FROM_UNIXTIME(1156219870);

日期转换为UNIX时间戳用函数: UNIX_TIMESTAMP()

Select UNIX_TIMESTAMP(’2006-11-04 12:23:00′);

例:mySQL查询当天的记录数:

$SQL=”select * from message Where DATE_FORMAT(FROM_UNIXTIME(chattime),’%Y-%m-%d’) = DATE_FORMAT(NOW(),’%Y-%m-%d’) order by id desc”;

当然大家也可以选择在PHP中进行转换UNIX时间戳转换为日期用函数: date()

date(‘Y-m-d H:i:s’, 1156219870);

日期转换为UNIX时间戳用函数:strtotime()

strtotime(‘2010-03-24 08:15:42’);

 

2016年8月22日 By mikel 分类: Android

来源: Android开发人员不得不收集的代码(不断更新中…) – /画家/ – 博客园

  • 尺寸相关
    • dp与px转换
    • sp与px转换
    • 各种单位转换
    • 在onCreate()即可获取View的宽高
    • ListView中提前测量View尺寸
  • 手机相关
    • 判断设备是否是手机
    • 获取当前设备的IMIE,需与上面的isPhone一起使用
    • 获取手机状态信息
    • 是否有SD卡
    • 获取MAC地址
    • 获取手机厂商,如Xiaomi
    • 获取手机型号,如MI2SC
    • 跳转至拨号界面
    • 拨打电话
    • 发送短信
    • 获取手机联系人
    • 直接打开手机联系人界面,并获取联系人号码
    • 获取手机短信并保存到xml中
  • 网络相关
    • 打开网络设置界面
    • 判断是否网络连接
    • 判断wifi是否连接状态
    • 获取移动网络运营商名称,如中国联通、中国移动、中国电信
    • 返回移动终端类型
    • 判断手机连接的网络类型(2G,3G,4G)
    • 判断当前手机的网络类型(WIFI还是2,3,4G)
  • App相关
    • 安装指定路径下的Apk
    • 卸载指定包名的App
    • 获取App名称
    • 获取当前App版本号
    • 获取当前App版本Code
    • 打开指定包名的App
    • 打开指定包名的App应用信息界面
    • 分享Apk信息
    • 获取App信息的一个封装类(包名、版本号、应用信息、图标、名称等)
    • 判断当前App处于前台还是后台
  • 屏幕相关
    • 获取手机分辨率
    • 获取状态栏高度
    • 获取状态栏高度+标题栏(ActionBar)高度
    • 获取屏幕截图
    • 设置透明状态栏,需在setContentView之前调用
  • 键盘相关
    • 避免输入法面板遮挡
    • 动态隐藏软键盘
    • 点击屏幕空白区域隐藏软键盘
    • 动态显示软键盘
    • 切换键盘显示与否状态
  • 正则相关
    • 正则工具类
  • 加解密相关
    • MD5加密
    • SHA加密
  • 未归类
    • 获取服务是否开启
  • 更新Log

尺寸相关

dp与px转换

复制代码
/**
* dp转px
*/
public static int dp2px(Context context, float dpValue) {
    final float scale = context.getResources().getDisplayMetrics().density;
    return (int) (dpValue * scale + 0.5f);
}

/**
* px转dp
*/
public static int px2dp(Context context, float pxValue) {
    final float scale = context.getResources().getDisplayMetrics().density;
    return (int) (pxValue / scale + 0.5f);
}
复制代码

sp与px转换

复制代码
/**
* sp转px
*/
public static int sp2px(Context context, float spValue) {
    final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
    return (int) (spValue * fontScale + 0.5f);
}

/**
* px转sp
*/
public static int px2sp(Context context, float pxValue) {
    final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
    return (int) (pxValue / fontScale + 0.5f);
}
复制代码

各种单位转换

复制代码
// 该方法存在于TypedValue
/**
* 各种单位转换
*/
public static float applyDimension(int unit, float value, DisplayMetrics metrics) {
    switch (unit) {
        case TypedValue.COMPLEX_UNIT_PX:
            return value;
        case TypedValue.COMPLEX_UNIT_DIP:
            return value * metrics.density;
        case TypedValue.COMPLEX_UNIT_SP:
            return value * metrics.scaledDensity;
        case TypedValue.COMPLEX_UNIT_PT:
            return value * metrics.xdpi * (1.0f / 72);
        case TypedValue.COMPLEX_UNIT_IN:
            return value * metrics.xdpi;
        case TypedValue.COMPLEX_UNIT_MM:
            return value * metrics.xdpi * (1.0f / 25.4f);
    }
    return 0;
}
复制代码

在onCreate()即可获取View的宽高

复制代码
/**
* 在onCreate()即可获取View的宽高
*/
public static int[] getViewMeasure(View view) {
    int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
    int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
    view.measure(widthMeasureSpec, heightMeasureSpec);
    return new int[]{view.getMeasuredWidth(), view.getMeasuredHeight()};
}
复制代码

ListView中提前测量View尺寸

复制代码
// 通知父布局,占用的宽,高;
/**
* ListView中提前测量View尺寸,如headerView
*/
private void measureView(View view) {
    ViewGroup.LayoutParams p = view.getLayoutParams();
    if (p == null) {
        p = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.WRAP_CONTENT);
    }
//第一个参数spec =0,mode是UNSPECIFIED(未指定),父元素不对子元素施加任何束缚,第二个参数,是外边距和内边距
int width = ViewGroup.getChildMeasureSpec(0, 0, p.width);
  int height; int tempHeight = p.height; 
  if (tempHeight > 0) { 
  height = MeasureSpec.makeMeasureSpec(tempHeight, MeasureSpec.EXACTLY); 
    } else { 
    height = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); 
    } 
  view.measure(width, height); 
}
复制代码

 

手机相关

判断设备是否是手机

复制代码
/**
* 判断设备是否是手机
*/
public static boolean isPhone(Context context) {
    TelephonyManager telephony = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
    return telephony.getPhoneType() != TelephonyManager.PHONE_TYPE_NONE;
}
复制代码

获取当前设备的IMIE,需与上面的isPhone一起使用

复制代码
/**
* 获取当前设备的IMIE,需与上面的isPhone一起使用
*/
public static String getDeviceIMEI(Context context) {
    String deviceId;
    if (isPhone(context)) {
        TelephonyManager telephony = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
        deviceId = telephony.getDeviceId();
    } else {
        deviceId = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID);
    }
    return deviceId;
}
复制代码

获取手机状态信息

复制代码
// 需添加权限<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
/**
* 获取手机状态信息
*
* 返回如下
* DeviceId(IMEI) = 99000311726612
* DeviceSoftwareVersion = 00
* Line1Number =
* NetworkCountryIso = cn
* NetworkOperator = 46003
* NetworkOperatorName = 中国电信
* NetworkType = 6
* honeType = 2
* SimCountryIso = cn
* SimOperator = 46003
* SimOperatorName = 中国电信
* SimSerialNumber = 89860315045710604022
* SimState = 5
* SubscriberId(IMSI) = 460030419724900
* VoiceMailNumber = *86
*/
public static String getPhoneStatus(Context context) {
    TelephonyManager tm = (TelephonyManager) context
            .getSystemService(Context.TELEPHONY_SERVICE);
    String str = "";
    str += "DeviceId(IMEI) = " + tm.getDeviceId() + "\n";
    str += "DeviceSoftwareVersion = " + tm.getDeviceSoftwareVersion() + "\n";
    str += "Line1Number = " + tm.getLine1Number() + "\n";
    str += "NetworkCountryIso = " + tm.getNetworkCountryIso() + "\n";
    str += "NetworkOperator = " + tm.getNetworkOperator() + "\n";
    str += "NetworkOperatorName = " + tm.getNetworkOperatorName() + "\n";
    str += "NetworkType = " + tm.getNetworkType() + "\n";
    str += "honeType = " + tm.getPhoneType() + "\n";
    str += "SimCountryIso = " + tm.getSimCountryIso() + "\n";
    str += "SimOperator = " + tm.getSimOperator() + "\n";
    str += "SimOperatorName = " + tm.getSimOperatorName() + "\n";
    str += "SimSerialNumber = " + tm.getSimSerialNumber() + "\n";
    str += "SimState = " + tm.getSimState() + "\n";
    str += "SubscriberId(IMSI) = " + tm.getSubscriberId() + "\n";
    str += "VoiceMailNumber = " + tm.getVoiceMailNumber() + "\n";
    return str;
}
复制代码

是否有SD卡

复制代码
/**
* 是否有SD卡
*/
public static boolean haveSDCard() {
    return android.os.Environment.getExternalStorageState().equals(android.os.Environment.MEDIA_MOUNTED);
}
复制代码

获取MAC地址

复制代码
// 需添加权限<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
/**
* 获取MAC地址
*/
public static String getMacAddress(Context context) {
    String macAddress;
    WifiManager wifi = (WifiManager) context
            .getSystemService(Context.WIFI_SERVICE);
    WifiInfo info = wifi.getConnectionInfo();
    macAddress = info.getMacAddress();
    if (null == macAddress) {
        return "";
    }
    macAddress = macAddress.replace(":", "");
    return macAddress;
}
复制代码

获取手机厂商,如Xiaomi

复制代码
/**
* 获取手机厂商,如Xiaomi
*/
public static String getOsName() {
    String MANUFACTURER = Build.MANUFACTURER;
    return MANUFACTURER;
}
复制代码

获取手机型号,如MI2SC

复制代码
/**
* 获取手机型号,如MI2SC
*/
private String getModel() {
    String model = android.os.Build.MODEL;
    if (model != null) {
        model = model.trim().replaceAll("\\s*", "");
    } else {
        model = "";
    }
    return model;
}
复制代码

跳转至拨号界面

复制代码
/**
* 跳转至拨号界面
*/
public static void callDial(Context context, String phoneNumber) {
    context.startActivity(new Intent(Intent.ACTION_DIAL, Uri.parse("tel:" + phoneNumber)));
}
复制代码

拨打电话

复制代码
/**
* 拨打电话
*/
public static void call(Context context, String phoneNumber) {
    context.startActivity(new Intent(Intent.ACTION_CALL, Uri.parse("tel:" + phoneNumber)));
}
复制代码

发送短信

复制代码
/**
* 发送短信
*/
public static void sendSms(Context context, String phoneNumber, String content) {
    Uri uri = Uri.parse("smsto:" + (TextUtils.isEmpty(phoneNumber) ? "" : phoneNumber));
    Intent intent = new Intent(Intent.ACTION_SENDTO, uri);
    intent.putExtra("sms_body", TextUtils.isEmpty(content) ? "" : content);
    context.startActivity(intent);
}
复制代码

获取手机联系人

复制代码
/**
* 获取手机联系人
*/
public static List<HashMap<String, String>> getAllContactInfo(Context context) {
    SystemClock.sleep(3000);
    ArrayList<HashMap<String, String>> list = new ArrayList<HashMap<String, String>>();
    // 1.获取内容解析者
    ContentResolver resolver = context.getContentResolver();
    // 2.获取内容提供者的地址:com.android.contacts
    // raw_contacts表的地址 :raw_contacts
    // view_data表的地址 : data
    // 3.生成查询地址
    Uri raw_uri = Uri.parse("content://com.android.contacts/raw_contacts");
    Uri date_uri = Uri.parse("content://com.android.contacts/data");
    // 4.查询操作,先查询raw_contacts,查询contact_id
    // projection : 查询的字段
    Cursor cursor = resolver.query(raw_uri, new String[] { "contact_id" },
            null, null, null);
    // 5.解析cursor
    while (cursor.moveToNext()) {
        // 6.获取查询的数据
        String contact_id = cursor.getString(0);
        // cursor.getString(cursor.getColumnIndex("contact_id"));//getColumnIndex
        // : 查询字段在cursor中索引值,一般都是用在查询字段比较多的时候
        // 判断contact_id是否为空
        if (!TextUtils.isEmpty(contact_id)) {//null   ""
            // 7.根据contact_id查询view_data表中的数据
            // selection : 查询条件
            // selectionArgs :查询条件的参数
            // sortOrder : 排序
            // 空指针: 1.null.方法 2.参数为null
            Cursor c = resolver.query(date_uri, new String[] { "data1",
                            "mimetype" }, "raw_contact_id=?",
                    new String[] { contact_id }, null);
            HashMap<String, String> map = new HashMap<String, String>();
            // 8.解析c
            while (c.moveToNext()) {
                // 9.获取数据
                String data1 = c.getString(0);
                String mimetype = c.getString(1);
                // 10.根据类型去判断获取的data1数据并保存
                if (mimetype.equals("vnd.android.cursor.item/phone_v2")) {
                    // 电话
                    map.put("phone", data1);
                } else if (mimetype.equals("vnd.android.cursor.item/name")) {
                    // 姓名
                    map.put("name", data1);
                }
            }
            // 11.添加到集合中数据
            list.add(map);
            // 12.关闭cursor
            c.close();
        }
    }
    // 12.关闭cursor
    cursor.close();
    return list;
}
复制代码

直接打开手机联系人界面,并获取联系人号码

复制代码
// 在按钮点击事件中设置Intent,
Intent intent = new Intent();
intent.setAction("android.intent.action.PICK");
intent.addCategory("android.intent.category.DEFAULT");
intent.setType("vnd.android.cursor.dir/phone_v2");
startActivityForResult(intent, 1);
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (data != null) {
        Uri uri = data.getData();
        String num = null;
        // 创建内容解析者
        ContentResolver contentResolver = getContentResolver();
        Cursor cursor = contentResolver.query(uri,
                null, null, null, null);
        while (cursor.moveToNext()) {
            num = cursor.getString(cursor.getColumnIndex("data1"));
        }
        cursor.close();
        num = num.replaceAll("-", "");//替换的操作,555-6 -> 5556
    }
}
复制代码

获取手机短信并保存到xml中

复制代码
/**
* 获取手机短信并保存到xml中
*/
public static void getAllSMS(Context context) {
    //1.获取短信
    //1.1获取内容解析者
    ContentResolver resolver = context.getContentResolver();
    //1.2获取内容提供者地址   sms,sms表的地址:null  不写
    //1.3获取查询路径
    Uri uri = Uri.parse("content://sms");
    //1.4.查询操作
    //projection : 查询的字段
    //selection : 查询的条件
    //selectionArgs : 查询条件的参数
    //sortOrder : 排序
    Cursor cursor = resolver.query(uri, new String[]{"address", "date", "type", "body"}, null, null, null);
    //设置最大进度
    int count = cursor.getCount();//获取短信的个数
    //2.备份短信
    //2.1获取xml序列器
    XmlSerializer xmlSerializer = Xml.newSerializer();
    try {
        //2.2设置xml文件保存的路径
        //os : 保存的位置
        //encoding : 编码格式
        xmlSerializer.setOutput(new FileOutputStream(new File("/mnt/sdcard/backupsms.xml")), "utf-8");
        //2.3设置头信息
        //standalone : 是否独立保存
        xmlSerializer.startDocument("utf-8", true);
        //2.4设置根标签
        xmlSerializer.startTag(null, "smss");
        //1.5.解析cursor
        while (cursor.moveToNext()) {
            SystemClock.sleep(1000);
            //2.5设置短信的标签
            xmlSerializer.startTag(null, "sms");
            //2.6设置文本内容的标签
            xmlSerializer.startTag(null, "address");
            String address = cursor.getString(0);
            //2.7设置文本内容
            xmlSerializer.text(address);
            xmlSerializer.endTag(null, "address");
            xmlSerializer.startTag(null, "date");
            String date = cursor.getString(1);
            xmlSerializer.text(date);
            xmlSerializer.endTag(null, "date");
            xmlSerializer.startTag(null, "type");
            String type = cursor.getString(2);
            xmlSerializer.text(type);
            xmlSerializer.endTag(null, "type");
            xmlSerializer.startTag(null, "body");
            String body = cursor.getString(3);
            xmlSerializer.text(body);
            xmlSerializer.endTag(null, "body");
            xmlSerializer.endTag(null, "sms");
            System.out.println("address:" + address + "   date:" + date + "  type:" + type + "  body:" + body);
        }
        xmlSerializer.endTag(null, "smss");
        xmlSerializer.endDocument();
        //2.8将数据刷新到文件中
        xmlSerializer.flush();
    } catch (Exception e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
}
复制代码

 

网络相关

打开网络设置界面

复制代码
/**
* 打开网络设置界面
*/
public static void openSetting(Activity activity) {
    Intent intent = new Intent("/");
    ComponentName cm = new ComponentName("com.android.settings",
            "com.android.settings.WirelessSettings");
    intent.setComponent(cm);
    intent.setAction("android.intent.action.VIEW");
    activity.startActivityForResult(intent, 0);
}
复制代码

判断是否网络连接

复制代码
/**
* 判断是否网络连接
*/
public static boolean isOnline(Context context) {
    ConnectivityManager manager = (ConnectivityManager) context
            .getSystemService(Activity.CONNECTIVITY_SERVICE);
    NetworkInfo info = manager.getActiveNetworkInfo();
    if (info != null && info.isConnected()) {
        return true;
    }
    return false;
}
复制代码

判断wifi是否连接状态

复制代码
/**
* 判断wifi是否连接状态
*/
public static boolean isWifi(Context context) {
    ConnectivityManager cm = (ConnectivityManager) context
            .getSystemService(Context.CONNECTIVITY_SERVICE);
    return cm != null && cm.getActiveNetworkInfo().getType() == ConnectivityManager.TYPE_WIFI;
}
复制代码

获取移动网络运营商名称,如中国联通、中国移动、中国电信

复制代码
/**
* 获取移动网络运营商名称,如中国联通、中国移动、中国电信
*/
public static String getNetworkOperatorName(Context context) {
    TelephonyManager telephonyManager = (TelephonyManager) context
            .getSystemService(Context.TELEPHONY_SERVICE);
    return telephonyManager.getNetworkOperatorName();
}
复制代码

返回移动终端类型

复制代码
// PHONE_TYPE_NONE :0 手机制式未知
// PHONE_TYPE_GSM :1 手机制式为GSM,移动和联通
// PHONE_TYPE_CDMA :2 手机制式为CDMA,电信
// PHONE_TYPE_SIP:3
/**
* 返回移动终端类型
*/
public static int getPhoneType(Context context) {
    TelephonyManager telephonyManager = (TelephonyManager) context
            .getSystemService(Context.TELEPHONY_SERVICE);
    return telephonyManager.getPhoneType();
}
复制代码

判断手机连接的网络类型(2G,3G,4G)

复制代码
// 联通的3G为UMTS或HSDPA,移动和联通的2G为GPRS或EGDE,电信的2G为CDMA,电信的3G为EVDO
public class Constants {
    /**
     * Unknown network class
     */
    public static final int NETWORK_CLASS_UNKNOWN = 0;
    /**
     * wifi net work
     */
    public static final int NETWORK_WIFI = 1;
    /**
     * "2G" networks
     */
    public static final int NETWORK_CLASS_2_G = 2;
    /**
     * "3G" networks
     */
    public static final int NETWORK_CLASS_3_G = 3;
    /**
     * "4G" networks
     */
    public static final int NETWORK_CLASS_4_G = 4;
}
/**
* 判断手机连接的网络类型(2G,3G,4G)
*/
public static int getNetWorkClass(Context context) {
    TelephonyManager telephonyManager = (TelephonyManager) context
            .getSystemService(Context.TELEPHONY_SERVICE);
    switch (telephonyManager.getNetworkType()) {
        case TelephonyManager.NETWORK_TYPE_GPRS:
        case TelephonyManager.NETWORK_TYPE_EDGE:
        case TelephonyManager.NETWORK_TYPE_CDMA:
        case TelephonyManager.NETWORK_TYPE_1xRTT:
        case TelephonyManager.NETWORK_TYPE_IDEN:
            return Constants.NETWORK_CLASS_2_G;
        case TelephonyManager.NETWORK_TYPE_UMTS:
        case TelephonyManager.NETWORK_TYPE_EVDO_0:
        case TelephonyManager.NETWORK_TYPE_EVDO_A:
        case TelephonyManager.NETWORK_TYPE_HSDPA:
        case TelephonyManager.NETWORK_TYPE_HSUPA:
        case TelephonyManager.NETWORK_TYPE_HSPA:
        case TelephonyManager.NETWORK_TYPE_EVDO_B:
        case TelephonyManager.NETWORK_TYPE_EHRPD:
        case TelephonyManager.NETWORK_TYPE_HSPAP:
            return Constants.NETWORK_CLASS_3_G;
        case TelephonyManager.NETWORK_TYPE_LTE:
            return Constants.NETWORK_CLASS_4_G;
        default:
            return Constants.NETWORK_CLASS_UNKNOWN;
    }
}
复制代码

判断当前手机的网络类型(WIFI还是2,3,4G)

复制代码
/**
* 判断当前手机的网络类型(WIFI还是2,3,4G),需要用到上面的方法
*/
public static int getNetWorkStatus(Context context) {
    int netWorkType = Constants.NETWORK_CLASS_UNKNOWN;
    ConnectivityManager connectivityManager = (ConnectivityManager) context
            .getSystemService(Context.CONNECTIVITY_SERVICE);
    NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
    if (networkInfo != null && networkInfo.isConnected()) {
        int type = networkInfo.getType();
        if (type == ConnectivityManager.TYPE_WIFI) {
            netWorkType = Constants.NETWORK_WIFI;
        } else if (type == ConnectivityManager.TYPE_MOBILE) {
            netWorkType = getNetWorkClass(context);
        }
    }
    return netWorkType;
}
复制代码

 

App相关

安装指定路径下的Apk

复制代码
/**
* 安装指定路径下的Apk
*/
public void installApk(String filePath) {
    Intent intent = new Intent();
    intent.setAction("android.intent.action.VIEW");
    intent.addCategory("android.intent.category.DEFAULT");
    intent.setDataAndType(Uri.fromFile(new File(filePath)), "application/vnd.android.package-archive");
    startActivityForResult(intent, 0);
}
复制代码

卸载指定包名的App

复制代码
/**
* 卸载指定包名的App
*/
public void uninstallApp(String packageName) {
    Intent intent = new Intent();
    intent.setAction("android.intent.action.DELETE");
    intent.addCategory("android.intent.category.DEFAULT");
    intent.setData(Uri.parse("package:" + packageName));
    startActivityForResult(intent, 0);
}
复制代码

获取App名称

复制代码
/**
* 获取App名称
*/
public static String getAppName(Context context) {
    try {
        PackageManager packageManager = context.getPackageManager();
        PackageInfo packageInfo = packageManager.getPackageInfo(
                context.getPackageName(), 0);
        int labelRes = packageInfo.applicationInfo.labelRes;
        return context.getResources().getString(labelRes);
    } catch (NameNotFoundException e) {
        e.printStackTrace();
    }
    return null;
}
复制代码

获取当前App版本号

复制代码
/**
* 获取当前App版本号
*/
public static String getVersionName(Context context) {
    String versionName = null;
    PackageManager pm = context.getPackageManager();
    PackageInfo info = null;
    try {
        info = pm.getPackageInfo(context.getApplicationContext().getPackageName(), 0);
    } catch (NameNotFoundException e) {
        e.printStackTrace();
    }
    if (info != null) {
        versionName = info.versionName;
    }
    return versionName;
}
复制代码

获取当前App版本Code

  • 复制代码
    /**
    *获取当前App版本Code
    */public static int getVersionCode(Context context) {
     int versionCode = 0;
     PackageManager pm = context.getPackageManager();
     PackageInfo info = null;
     try {
      info = pm.getPackageInfo(context.getApplicationContext().getPackageName(), 0);
     } catch (PackageManager.NameNotFoundException e) {
      e.printStackTrace();
     }
     if (info != null) {
      versionCode = info.versionCode;
     }
     return versionCode;
    }
     
    
    /**
    *打开指定包名的App
    */
    public void openOtherApp(String packageName){
     PackageManager manager = getPackageManager();
     Intent launchIntentForPackage = manager.getLaunchIntentForPackage(packageName);
     if (launchIntentForPackage != null) {
      startActivity(launchIntentForPackage);
     }
    }
    
    复制代码

打开指定包名的App应用信息界面

复制代码
/**
* 打开指定包名的App应用信息界面
*/
public void showAppInfo(String packageName) {
    Intent intent = new Intent();
    intent.setAction("android.settings.APPLICATION_DETAILS_SETTINGS");
    intent.setData(Uri.parse("package:" + packageName));
    startActivity(intent);
}
复制代码

分享Apk信息

复制代码
/**
* 分享Apk信息
*/
public void shareApkInfo(String info) {
    Intent intent = new Intent();
    intent.setAction("android.intent.action.SEND");
    intent.addCategory("android.intent.category.DEFAULT");
    intent.setType("text/plain");
    intent.putExtra(Intent.EXTRA_TEXT, info);
    startActivity(intent);
}
复制代码

获取App信息的一个封装类(包名、版本号、应用信息、图标、名称等)

复制代码
/**
* 获取App信息的一个封装类(包名、版本号、应用信息、图标、名称等)
*/
public class AppEnging {
    public static List<AppInfo> getAppInfos(Context context) {
        List<AppInfo> list = new ArrayList<AppInfo>();
        //获取应用程序信息
        //包的管理者
        PackageManager pm = context.getPackageManager();
        //获取系统中安装到所有软件信息
        List<PackageInfo> installedPackages = pm.getInstalledPackages(0);
        for (PackageInfo packageInfo : installedPackages) {
            //获取包名
            String packageName = packageInfo.packageName;
            //获取版本号
            String versionName = packageInfo.versionName;
            //获取application
            ApplicationInfo applicationInfo = packageInfo.applicationInfo;
            int uid = applicationInfo.uid;
            //获取应用程序的图标
            Drawable icon = applicationInfo.loadIcon(pm);
            //获取应用程序的名称
            String name = applicationInfo.loadLabel(pm).toString();
            //是否是用户程序
            //获取应用程序中相关信息,是否是系统程序和是否安装到SD卡
            boolean isUser;
            int flags = applicationInfo.flags;
            if ((applicationInfo.FLAG_SYSTEM & flags) == applicationInfo.FLAG_SYSTEM) {
                //系统程序
                isUser = false;
            } else {
                //用户程序
                isUser = true;
            }
            //是否安装到SD卡
            boolean isSD;
            if ((applicationInfo.FLAG_EXTERNAL_STORAGE & flags) == applicationInfo.FLAG_EXTERNAL_STORAGE) {
                //安装到了SD卡
                isSD = true;
            } else {
                //安装到手机中
                isSD = false;
            }
            //添加到bean中
            AppInfo appInfo = new AppInfo(name, icon, packageName, versionName, isSD, isUser);
            //将bean存放到list集合
            list.add(appInfo);
        }
        return list;
    }
}

// 封装软件信息的bean类
class AppInfo {
    //名称
    private String name;
    //图标
    private Drawable icon;
    //包名
    private String packagName;
    //版本号
    private String versionName;
    //是否安装到SD卡
    private boolean isSD;
    //是否是用户程序
    private boolean isUser;

    public AppInfo() {
        super();
    }

    public AppInfo(String name, Drawable icon, String packagName,
                   String versionName, boolean isSD, boolean isUser) {
        super();
        this.name = name;
        this.icon = icon;
        this.packagName = packagName;
        this.versionName = versionName;
        this.isSD = isSD;
        this.isUser = isUser;
    }
}
复制代码

判断当前App处于前台还是后台

复制代码
// 需添加<uses-permission android:name="android.permission.GET_TASKS"/>
// 并且必须是系统应用该方法才有效
/**
* 判断当前App处于前台还是后台
*/
public static boolean isApplicationBackground(final Context context) {
    ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
    @SuppressWarnings("deprecation")
    List<ActivityManager.RunningTaskInfo> tasks = am.getRunningTasks(1);
    if (!tasks.isEmpty()) {
        ComponentName topActivity = tasks.get(0).topActivity;
        if (!topActivity.getPackageName().equals(context.getPackageName())) {
            return true;
        }
    }
    return false;
}
复制代码

 

屏幕相关

获取手机分辨率

复制代码
/**
* 获取屏幕的宽度px
*/
public static int getDeviceWidth(Context context) {
    WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
    DisplayMetrics outMetrics = new DisplayMetrics();// 创建了一张白纸
    windowManager.getDefaultDisplay().getMetrics(outMetrics);// 给白纸设置宽高
    return outMetrics.widthPixels;
}

/**
* 获取屏幕的高度px
*/
public static int getDeviceHeight(Context context) {
    WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
    DisplayMetrics outMetrics = new DisplayMetrics();// 创建了一张白纸
    windowManager.getDefaultDisplay().getMetrics(outMetrics);// 给白纸设置宽高
    return outMetrics.heightPixels;
}
复制代码

获取状态栏高度

复制代码
/**
* 获取状态栏高度
*/
public int getStatusBarHeight() {
    int result = 0;
    int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
    if (resourceId > 0) {
        result = getResources().getDimensionPixelSize(resourceId);
    }
    return result;
}
复制代码

获取状态栏高度+标题栏(ActionBar)高度

复制代码
/**
* 获取状态栏高度+标题栏(ActionBar)高度
*/
public static int getTopBarHeight(Activity activity) {
    return activity.getWindow().findViewById(Window.ID_ANDROID_CONTENT).getTop();
}
复制代码

获取屏幕截图

复制代码
/**
* 获取当前屏幕截图,包含状态栏
*/
public static Bitmap snapShotWithStatusBar(Activity activity) {
    View view = activity.getWindow().getDecorView();
    view.setDrawingCacheEnabled(true);
    view.buildDrawingCache();
    Bitmap bmp = view.getDrawingCache();
    int width = getScreenWidth(activity);
    int height = getScreenHeight(activity);
    Bitmap bp = null;
    bp = Bitmap.createBitmap(bmp, 0, 0, width, height);
    view.destroyDrawingCache();
    return bp;
}

/**
* 获取当前屏幕截图,不包含状态栏
*/
public static Bitmap snapShotWithoutStatusBar(Activity activity) {
    View view = activity.getWindow().getDecorView();
    view.setDrawingCacheEnabled(true);
    view.buildDrawingCache();
    Bitmap bmp = view.getDrawingCache();
    Rect frame = new Rect();
    activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(frame);
    int statusBarHeight = frame.top;
    int width = getScreenWidth(activity);
    int height = getScreenHeight(activity);
    Bitmap bp = null;
    bp = Bitmap.createBitmap(bmp, 0, statusBarHeight, width, height
            - statusBarHeight);
    view.destroyDrawingCache();
    return bp;
}
复制代码

设置透明状态栏,需在setContentView之前调用

复制代码
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
    //透明状态栏
    getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
    //透明导航栏
    getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
}

// 需在顶部控件布局中加入以下属性让内容出现在状态栏之下
android:clipToPadding="true" 
android:fitsSystemWindows="true"
复制代码

 

键盘相关

避免输入法面板遮挡

// 在manifest.xml中activity中设置
android:windowSoftInputMode="stateVisible|adjustResize"

动态隐藏软键盘

复制代码
/**
* 动态隐藏软键盘
*/
public static void hideSoftInput(Activity activity) {
    View view = activity.getWindow().peekDecorView();
    if (view != null) {
        InputMethodManager inputmanger = (InputMethodManager) activity
                .getSystemService(Context.INPUT_METHOD_SERVICE);
        inputmanger.hideSoftInputFromWindow(view.getWindowToken(), 0);
    }
}

/**
* 动态隐藏软键盘
*/
public static void hideSoftInput(Context context, EditText edit) {
    edit.clearFocus();
    InputMethodManager inputmanger = (InputMethodManager) context
            .getSystemService(Context.INPUT_METHOD_SERVICE);
    inputmanger.hideSoftInputFromWindow(edit.getWindowToken(), 0);
}
复制代码

点击屏幕空白区域隐藏软键盘

复制代码
// 方法1:在onTouch中处理,未获焦点则隐藏
/**
* 在onTouch中处理,未获焦点则隐藏
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
    if (null != this.getCurrentFocus()) {
        InputMethodManager mInputMethodManager = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
        return mInputMethodManager.hideSoftInputFromWindow(this.getCurrentFocus().getWindowToken(), 0);
    }
    return super.onTouchEvent(event);
}

// 方法2:根据EditText所在坐标和用户点击的坐标相对比,来判断是否隐藏键盘,需重写dispatchTouchEvent
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        View v = getCurrentFocus();
        if (isShouldHideKeyboard(v, ev)) {
            hideKeyboard(v.getWindowToken());
        }
    }
    return super.dispatchTouchEvent(ev);
}

/**
* 根据EditText所在坐标和用户点击的坐标相对比,来判断是否隐藏键盘
*/
private boolean isShouldHideKeyboard(View v, MotionEvent event) {
    if (v != null && (v instanceof EditText)) {
        int[] l = {0, 0};
        v.getLocationInWindow(l);
        int left = l[0],
                top = l[1],
                bottom = top + v.getHeight(),
                right = left + v.getWidth();
        return !(event.getX() > left && event.getX() < right
                && event.getY() > top && event.getY() < bottom);
    }
    return false;
}

/**
* 获取InputMethodManager,隐藏软键盘
*/
private void hideKeyboard(IBinder token) {
    if (token != null) {
        InputMethodManager im = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
        im.hideSoftInputFromWindow(token, InputMethodManager.HIDE_NOT_ALWAYS);
    }
}
复制代码

动态显示软键盘

复制代码
/**
* 动态显示软键盘
*/
public static void showSoftInput(Context context, EditText edit) {
    edit.setFocusable(true);
    edit.setFocusableInTouchMode(true);
    edit.requestFocus();
    InputMethodManager inputManager = (InputMethodManager) context
            .getSystemService(Context.INPUT_METHOD_SERVICE);
    inputManager.showSoftInput(edit, 0);
}
复制代码

切换键盘显示与否状态

复制代码
/**
* 切换键盘显示与否状态
*/
public static void toggleSoftInput(Context context, EditText edit) {
    edit.setFocusable(true);
    edit.setFocusableInTouchMode(true);
    edit.requestFocus();
    InputMethodManager inputManager = (InputMethodManager) context
            .getSystemService(Context.INPUT_METHOD_SERVICE);
    inputManager.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);
}
复制代码

 

正则相关

正则工具类

复制代码
public class RegularUtils {
    //验证手机号
    private static final String REGEX_MOBILE = "^(13[0-9]|14[5|7]|15[0|1|2|3|5|6|7|8|9]|18[0|1|2|3|5|6|7|8|9])\\d{8}$";
    //验证座机号,正确格式:xxx/xxxx-xxxxxxx/xxxxxxxx
    private static final String REGEX_TEL = "^0\\d{2,3}[- ]?\\d{7,8}";
    //验证邮箱
    private static final String REGEX_EMAIL = "^\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*$";
    //验证url
    private static final String REGEX_URL = "http(s)?://([\\w-]+\\.)+[\\w-]+(/[\\w-./?%&=]*)?";
    //验证汉字
    private static final String REGEX_CHZ = "^[\\u4e00-\\u9fa5]+$";
    //验证用户名,取值范围为a-z,A-Z,0-9,"_",汉字,不能以"_"结尾,用户名必须是6-20位
    private static final String REGEX_USERNAME = "^[\\w\\u4e00-\\u9fa5]{6,20}(?<!_)$";
    //验证IP地址
    private static final String REGEX_IP = "((2[0-4]\\d|25[0-5]|[01]?\\d\\d?)\\.){3}(2[0-4]\\d|25[0-5]|[01]?\\d\\d?)";

    //If u want more please visit http://toutiao.com/i6231678548520731137/

    /**
     * @param string 待验证文本
     * @return 是否符合手机号格式
     */
    public static boolean isMobile(String string) {
        return isMatch(REGEX_MOBILE, string);
    }

    /**
     * @param string 待验证文本
     * @return 是否符合座机号码格式
     */
    public static boolean isTel(String string) {
        return isMatch(REGEX_TEL, string);
    }

    /**
     * @param string 待验证文本
     * @return 是否符合邮箱格式
     */
    public static boolean isEmail(String string) {
        return isMatch(REGEX_EMAIL, string);
    }

    /**
     * @param string 待验证文本
     * @return 是否符合网址格式
     */
    public static boolean isURL(String string) {
        return isMatch(REGEX_URL, string);
    }

    /**
     * @param string 待验证文本
     * @return 是否符合汉字
     */
    public static boolean isChz(String string) {
        return isMatch(REGEX_CHZ, string);
    }

    /**
     * @param string 待验证文本
     * @return 是否符合用户名
     */
    public static boolean isUsername(String string) {
        return isMatch(REGEX_USERNAME, string);
    }

    /**
     * @param regex  正则表达式字符串
     * @param string 要匹配的字符串
     * @return 如果str 符合 regex的正则表达式格式,返回true, 否则返回 false;
     */
    public static boolean isMatch(String regex, String string) {
        return !TextUtils.isEmpty(string) && Pattern.matches(regex, string);
    }
}
复制代码

 


 

加解密相关

MD5加密

复制代码
/**
* MD5加密
*/
public static String encryptMD5(String data) throws Exception {
    MessageDigest md5 = MessageDigest.getInstance("MD5");
    return new BigInteger(md5.digest(data.getBytes())).toString(16);
}
复制代码

SHA加密

复制代码
/**
* SHA加密
*/
public static String encryptSHA(String data) throws Exception {
    MessageDigest sha = MessageDigest.getInstance("SHA");
    return new BigInteger(sha.digest(data.getBytes())).toString(32);
}
复制代码

 

未归类

获取服务是否开启

复制代码
/**
* 获取服务是否开启
*/
public static boolean isRunningService(String className, Context context) {
    //进程的管理者,活动的管理者
    ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
    //获取正在运行的服务
    List<RunningServiceInfo> runningServices = activityManager.getRunningServices(1000);//maxNum 返回正在运行的服务的上限个数,最多返回多少个服务
    //遍历集合
    for (RunningServiceInfo runningServiceInfo : runningServices) {
        //获取控件的标示
        ComponentName service = runningServiceInfo.service;
        //获取正在运行的服务的全类名
        String className2 = service.getClassName();
        //将获取到的正在运行的服务的全类名和传递过来的服务的全类名比较,一直表示服务正在运行  返回true,不一致表示服务没有运行  返回false
        if (className.equals(className2)) {
            return true;
        }
    }
    return false;
}
复制代码

 

Android开发人员不得不收集的代码(不断更新中…) – /画家/ – 博客园已关闭评论
2016年8月22日 By mikel 分类: 数据库

来源: 各种主流 SQLServer 迁移到 MySQL 工具对比 – 青出于蓝 – 博客园

      我之所以会写这篇对比文章,是因为公司新产品研发真实经历过这个痛苦过程(传统基于SQL Server开发的C/S产品转为MySQL云产品)。 首次需要数据转换是测试环节,当时为了快速验证新研发云产品性能与结果准确性(算法类),所以需大量的原始数据,最快的办法就是使用老产品的真实数据。因 为在前期数据转换时主用于内部验证,并没有花很多心思去处理这个事情,一般数据能导过去,不对的地方自己再手工处理一下就好了。后面对这个转换工具引起了 极大的重视是正式有老客户升级时,因为正式投入使用就容不得半点错误(当时至少有几百家客户需要升级新产品),所以数据转移第一要求是百分百的准确率,其 次是速度要快。现在回想起来,当时要有这么一篇对比文章,那我就不会浪费那么多时间在查找、对比、验证工具和数据维护修正上了,所以真心希望通过这篇对比 文章能给大家提供一些参考或帮助!下面进入正题:
      在部署前期,首要任务就是考虑如何快速把基于 SQL Server 数据库的应用程序移植到阿里云的 MySQL 数据库。由于程序是基于 O/R mapping 编写,并且数据库中没有使用存储过程、用户函数等数据库功能,因此仅仅需要考虑的是数据库中的数据如何转换到新的 MySQL 数据库中。
      通过度娘查找,找到如下四种可以使用的工具,并且每一种工具都有大量的用户,还有不少用户在自已的博客中写下了图文使用经验,这四种工具分别是:
      由于公司需要处理的是业务数据库,因此必须保证数据转换的准确率(不允许丢失数据,数据库字段、索引完整),并且需要保证数据库迁移后能立即使用。因 此在实施数据迁移前,对这几种 SQLServer 到 MySQL 的迁移工具进行一个全面测试。下面我们将基于以下需求为前提进行测试:

● 软件易用性
● 处理速度和内存占用
● 数据完整性
● 试用版限制
● 其它功能

一、测试用的源数据库和系统
      用于测试的源数据库名为 MesoftReportCenter。由于其中一个测试工具试用版限制只能处理两张数据表的原因,因此我们只选取了记录数最多的两张数据 表:HISOPChargeIntermediateResult 和 HISOPChargeItemIntermediateResult。两张数据表合计的记录数约为 328万,数据库不算大,但针对本次进行测试也基本上足够了。
       SQLServer 服务器和 MySQL 服务器分别运行在两台独立的虚拟机系统中,而所有的待测试程序都运行在 MySQL 所在的服务器上面。其中:

      SQLServer 服务配置:

● 操作系统:Windows XP
● 内  存:2GB
● 100MB 电信光纤

MySQL 服务配置:

● 操作系统:Windows XP
● 内  存:1GB
● 100MB 电信光纤

      同时为了测试的公平性,除 Mss2SQL 外,所有软件都是直接从官网下载最新的版本。 Mss2SQL 由于试用版的限制原因没有参与测试,而使用了网上唯一能找到的 5.3 破解版进行测试。
二、软件易用性评测
      软件易用性主要是指软件在导入前的配置是否容易。由于很多软件设计是面向程序员而非一般的数据库管理人员、甚至是普通的应用程序实施人员,而这一类人员很 多时候并没有数据源配置经验。因为一些使用 ODBC 或者 ADO 进行配置的程序往往会让这类用户造成困扰(主要是不知道应该选择什么类型的数据库驱动程序)。下面让我们看看四个工具的设计界面:
1、SQLyog
 

SQLyog 使用的是古老的 ODBC 连接,但对于新一代的程序来说,这种方式的非常的不熟悉并且不容易使用,并且必须要求本机安装好相应的数据库的 ODBC 驱动程序(SQL Server 一般自带好)。

2、Navicat Premium

Navicat Premium 是四个应用工具中设计最不人性化的一个:从上图怎么也想像不到要点按那个小按钮来添加一个新的连接,并且这个连接设置不会保存,每次导入时都必须重新设 置。 Navicat Premium 使用的是比 ODBC 稍先进的 ADO 设置方式(199X年代的产物),但使用上依然是针对老一代的程序员。

3、Mss2sql
      Mss2sql 是最容易在百度上搜索出来的工具,原因之一是它出现的时间较早。
 

Mss2sql 由于是很有针对性的从 SQLServer 迁移到 MySQL,因为界面使用了操作向导设计,使用非常容易。同时在设置的过程中,有非常多的选项进行细节调整,可以感觉到软件经过了相当长一段时间的使用渐渐完善出来的。

4、DB2DB
 

DB2DB 由于是由国人开发,因此无论是界面还是提示信息,都是全程汉字。另外,由于 DB2DB 在功能上很有针对性,因为界面设计一目了然和易使用。和 mss2sql 一样, DB2DB 提供了非常多的选项供用户进行选择和设置。

 

三、处理速度和内存占用评测
      在本评测前,本人的一位资深同事曾经从网上下载了某款迁移软件,把一个大约2500万记录数的数据表转送到阿里云 MySQL,结果经过了三天三夜(好在其中两天是星期六和星期日两个休息日)都未能迁移过来。因此这一次需要对这四个工具的处理速度作一个详细的测试。
      考虑到从 SQL Server 迁移到 MySQL 会出现两种不同的场景:

● 从 SQL Server 迁移到本地 MySQL 进行代码测试和修改;
● 从 SQL Server 迁移到云端 MySQL 数据库正式上线使用;

      因此我们的测试也会针对这两个场景分别进行评测,测试结果如下(记录数约为 328万):
工具名称 迁移到本地耗时 迁移到云端耗时 最高CPU占用 内存占用
SQLyog 2806秒 4438秒 08% 20MB
Navicat Premium 598秒 3166秒 52% 32MB
Mss2sql 726秒 1915秒 30% 12MB
DB2DB 164秒 1282秒 34% 40MB
注:红色字体标识为胜出者。
以下为测试过程中的截图:
1、SQLyog

2、Navicat Premium
 

注意:我们在测试 Navicat Premium 迁移到  MySQL 时发现,对于 SQL Server 的 Money 类型支持不好(不排除还有其它的数据类型支持不好)。Money 类型字段默认的小数位长度为 255,使得无法创建数据表导致整个测试无法成功,需要我们逐张表进行表结构修改才能完成测试过程。
       Navicat Premium 的处理速度属于中等,不算快也不算慢,但 CPU 占用还有内存占用都处于高位水平。不过以现在的电脑硬件水平来说,还是可以接受。但 CPU 占用率太高,将使得数据在导入的过程中,服务器不能用于其它用途。

3、Mss2sql
Mss2sql 并没有提供计时器,因此我们使用人工计时的方法,整个过程处理完毕大于是 726 秒。Mss2sql 的 CPU 占用率相对其它工具来说较高,但仍属于可以接受的范围之内。

4、DB2DB
 

DB2DB 同样迁移 300万数据时,仅仅使用了 2 分 44 秒,这个速度相当惊人。不过最后的结果出现一个 BUG,就是提示了转换成功,但后面的进度条却没有走完(在后面的数据完整性评测中,我们验证了数据其实是已经全部处理完毕了)。

四、数据完整性评测
      把数据准确无误地从 SQL Server 迁移到 MySQL 应该作为这些工具的一个基本要求,因此这里我们对四种工具转换之后的结果进行检查。
      我们通过后台 SQL 对记录数进行检查,发现所有的工具都能把记录完整地迁移到新的数据库。如果仔细观察,可以发现上图中各个数据库的大小是不一致的,基本的判断是由于各种工 具在映射数据表字段时,字段长度取值可能不能而引起的。而 mesoftreportcenter2 数据库大小比起其它数据库差不多少了一半,这引起了我们的注意。通过分析,我们发现 Navicat Premium 在迁移数据库时,并不会为该数据库所有数据表创建索引和主键,缺少索引和主键的数据库大小显然比其它数据库要少得多。
 
      为了解各工具迁移后的数据库能否立即应用于生产环境,我们对创建后的数据表进行了更深入的分析,发现各工具对字段默认值的支持程度也不尽相同。其中:

● SQLyog:完整支持 SQL Server 的默认值;
● Navicat Premium:完全不支持默认值,所有迁移后的数据表都没有默认值;
● Mss2sql:支持默认值但有严重错误;
● DB2DB:完整支持 SQL Server 的默认值。

      Mss2sql 的默认值有一个严重的错误,在 SQL Server 中字段默认值为空字符串 ”,但迁移之后变成两个 ” 符号。Mss2sql 这个严重的错误会使得程序在正式环境运行后,数据库会产生错误的数据!
 
      在一些老旧的系统中,数据库还会存在 Text、二进制类型的字段数据,通过测试对比后,四种工具都完美支持 Text 和 二进制(Image)类型字段。
小结:
测试项目 SQLyog Navicat Premium Mss2sql DB2DB
表结构 支持 支持 支持 支持
字段长度 支持 部分支持(对Money等支持不好) 支持 支持
数据 完整 完整 完整 完整
索引 支持 不支持 支持 支持
关键字 支持 不支持 支持 支持
默认值 支持 不支持 支持,但有严重错误 支持
二进制数据 支持 支持 支持 支持
五、各工具其它功能及试用版限制
      估计由于数据库同步会存在一些技术难题的原因,4 款工具目前都是只是提供试用版本,最后我们来看看四个工具的试用版本各自的限制是什么:
工具名 价格 试用限制 其它功能 备注
SQyog $199 30天试用,并且只允许转换两张数据表
Navicat Premium $799
Mss2sql $49 每张数据表只允许有50秒处理时间 支持导出为 SQL
DB2DB ¥199 10万记录限制 支持导出为 SQL
      四种工具中,由于 SQLyog 和 Navicat Premium 提供了额外的管理功能,所以价格相比其它两款工具的要高得多。特别是 Navicat,必须是 Premium 版本才提供数据转换的功能。而 Mss2sql 最新版本的试用版只提供了 50 秒处理时间,因为实用价值不大。而笔者与 DB2DB 作者联系时得知,DB2DB 设置 10万记录限制,主要是考虑国内很多小型软件记录数都是少于 10 万笔,而这一类人群一般都是小型创业团队。
六、评测总结
      最后,对四款软件的测试结果作一个整体的总结:
工具名 处理速度 数据完整性 价格 推荐度
SQLyog ★☆☆☆☆ ★★★★★ ★★☆☆☆ ★★☆☆☆
Navicat Premium ★★★☆☆ ★☆☆☆☆ ★☆☆☆☆ ★☆☆☆☆
Mss2sql ★★☆☆☆ ★★★☆☆ ★★★★☆ ★★★☆☆
DB2DB ★★★★★ ★★★★★ ★★★★★ ★★★★★
      以上四款软件中,最不推荐使用的是 Navicat Premium,主要原因是数据的完整性表现较差,转换后的数据不能立即用于生产环境,需要程序员仔细自行查找原因和分析。而 SQLyog 有较好的数据完整性,但整体处理速度非常的慢,如果数据较大的情况下,需要浪费非常多宝贵的时间。比较推荐的是 DB2DB,软件整体表现较好,对我来说最重要的是在不购买的情况下也够用了,而且全中文的傻瓜式界面操作起来实在方便。
各种主流 SQLServer 迁移到 MySQL 工具对比 – 青出于蓝 – 博客园已关闭评论
2016年8月22日 By mikel 分类: PHP, 开发笔记
PHP中的date函数可以用来进行时间控制。
首先需要用date_default_timezone_set来设置一个时区,比如说:
date_default_timezone_set("PRC");
实现加一个月
date("Y-m-d",strtotime("+1months",strtotime("2015-11-27"))); 
//结果是 2015-12-27
实现加一周
改成 +1week
即date("Y-m-d",strtotime("+1week",strtotime("2015-11-27")));

在PHP中怎么将date()函数转换为unix时间戳?

简单.
形式一:
$time strtotime('2010-03-24 08:15:42');
形式二:
$date =  date('Y-m-d H:i:s');
$time strtotime($date );
PHP中如何给日期加上一个月 加一周已关闭评论
2016年8月19日 By mikel 分类: PHP

来源: ThinkPHP教程_PHP框架之ThinkPHP(一)【入门和介绍、ThinkPHP版本和文件夹规范、项目入口文件】 – TigerYangWTH – 博客园

一、什么是框架

就是别人写好的一批类和一个规则

注意,不仅仅是在学习中,更是在以后的工作中,在选择框架的时候,不要一味地追求”大”、”全”,而是要根据当时项目的需要选择合适的框架

1、MVC模式

M  Model  模型  就是数据库操作类(通过数据库操作类去操作各个表)

V  View    视图  模板

C  Controller控制器 控制器来实现模板、模型之间的控制关系

控制器也是一个类,这个类中有不同的方法,所以控制器也叫模块(Module),其中的方法叫动作(Action)!

那么问题来了,视图是如何告知哪个控制器(模块)去执行哪一个方法(动作)呢?结果是URL

比如说在一个项目中有如下两个模块

用户管理  (用户注册、用户修改、用户删除、用户编辑)

商品管理  (用户下单、修改订单、取消订单)

那么就得分为两个控制器,一个是用户管理控制器、另一个是商品管理控制器!然后通过URL去告知相应的控制器去执行相应的方法,比如说http://localhost/index.php?m=user&a=reg就表示告知用户管理控制器(模块)去执行用户注册方法(动作).PS,URL中的m表示Module,a表示Action;index.php叫做主入口文件,注意,所有文件加载都以主入口为准,也就是说必须通过主入口文件去告知某某控制器去执行某某方法!

 

二、ThinkPHP框架

1、具有比较好的跨平台性

无论是服务器平台(Linux、Windows、Unix)还是服务器软件(Apache、IIS、Nginx),都能良好的支持

2、文件名大小写问题

Windows中的文件名是不区分大小写的,而Linux和Unix中的文件名是区分大小写的!那么就可能在平台移植过程中,出现文件找不到的情况,而ThinkPHP很好的解决了这个问题,只要在ThinkPHP中开启了APP_Debug,即调试模式,那么即使在Windows下开发,ThinkPHP就已经严格区分了大小,这就从源头上解决了平台移植大小写的问题!

3、URL支持多种模式

共有4种模式,方便SEO

4、支持自动加载、动态编译

节约系统开销

5、AJAX支持、视图、分组、权限管理、关联操作、主从数据库、缓存(apc、db、memcache、shmop、xcache、file)

6、自动验证、自动完成、映射

7、加载第三方类库

三、ThinkPHP版本和文件夹规范

1、官网:www.thinkPHP.cn

2、软件开发阶段

A  alpha  内部测试

B  beta   公开测试

C  RC   开发倒计时(该阶段已经将重大和重要的BUG进行了修复,仅仅会有一些小的使用性上面的小bug)

D  final   正式发行版本

 3、ThinkPHP版本

基础阶段学习的是2.1RC版(目前(2016-08-18)在官网上已经没有2.1RC版了,只有2.1正式版),所以使用的是2.1正式版

ThinkPHP有核心包和完整包之分,完全开发手册有PDF、swf、CHM版,还有ThinkPHP的API手册

核心包  仅仅包含ThinkPHP运行的最主要文件(不包含扩展类、示例、文档)(ThinkPHP_2.1_core.zip)

完整包  核心包的基础上增加了扩展类、示例、文档

ps,其实2.x版本的完整包是有带示例和文档完整包(ThinkPHP_2.1_full.zip)和带扩展、示例和文档完整包(ThinkPHP_2.1_full_with_extend.zip)之分的,但是在3.x以及5.x版本中,完整包就没有分两种了(统一叫ThinkPHP_3.1.3_full.zip)

注意,核心包仅仅只有一个ThinkPHP的核心目录,而且完整包和核心包的ThinkPHP目录也是不一样的!完整包的ThinkPHP目录中的Vender目录下是有很多扩展类的,而核心包的ThinkPHP目录中的Vender目录是空的;完整包的ThinkPHP目录中的Lib目录下是有一ORG目录的,而核心包是没有的!

    

    

文档下载

4、文件夹规范

  ThinkPHP的文件夹规范分两大块,一个是系统目录结构、一个是项目目录结构

    ·系统目录结构

#ThinkPHP.php文件:项目初始化时,单一入口文件必须引入的一个文件,因为系统目录必须通过这个文件查找

#Common目录:一些公用的函数,比如说D()、F()等等;其中的convention.php包含ThinkPHP中所有的配置文件需要用到的东西等等

#Lang目录:语言包,就是将ThinkPHP可能产生的一些错误和异常与对应的文字说明做一个映射。而文字说明可以是中文、英文或其它语言,如果是中文就是中文包,英文就是英文包等等

#Lib目录:包含如下两个目录

Think目录

Db目录中的Driver目录就相当于数据库抽象层,它屏蔽了连接不同数据库的不同方法,统一为一个名称。可以直接编写一个数据库连接类,比如说DbMongo.class.php,但是一定要注意命名规范

Exception目录中是异常处理类

Template目录中是模板解析类。这里讨论一个问题,就是在ThinkPHP中是使用 Vendor目录中加载的第三方类库(比如说Smarty)来进行模板解析,还是使用ThinkPHP自带的类库?答案是,推荐使用ThinkPHP自带 的类库进行模板解析,原因有两个(1、兼容性更好 2、模板引擎和标签处理是XML方式的,处理效率更高) 

Util目录与缓存CookieSession等等有关  

ORG目录

Crypt目录与加密有关

Io目录与目录处理有关

Net目录与网络处理有关

Util目录RBACSocket遍历分页等有关

#Model目录:框架模式扩展目录

#Tpl目录:系统处理模板目录

#Vendor目录:第三方类库

#LICENSE.txt文件:版权声明

·项目目录结构

项目初始化操作,在项目目录下创建一个index.php(文件名随意,最好与项目中的子项目名相同,比如说项目前台用index.php、项目后台用admin.php,这就是单一入口文件。一般来说,一个项目都有两个子项目,即前台子项目和后台子项目,但是如果用了项目分组的话,一个子项目就是一个分组,所以就没有子项目了,也就只有一个单入口文件。ps,单一入口文件名一般小写,而项目名首字母要大写),并写如下代码,再访问即可!

#Common目录:自己写的公用函数目录

#Config目录:配置文件目录

#Lang目录:作用同系统目录结构中的Lang目录

#Lib目录:分为如下两个目录,通常还有第三个目录Org,该目录在项目初始化的过程中是不会自动生成的,可以手动创建

Action目录:控制器目录

Model目录:模型目录,即自定义数据库类

Org目录:扩展类,可以直接将系统目录中的ORG目录下的扩展类Copy过来用,也可以自己写一些扩展类

#Runtime目录:

Cache目录:编译出来的页面(即编译后模板)

Data目录:与表相关的数据

Logs目录:错误日志、运行日志

Temp目录:文件缓存

~app.php文件和~runtime.php文件都是系统生成的核心编译文件

#Tpl目录:Tpl目录下,一个目录就是一套皮肤,默认有一个default目录

四、项目主入口文件

1、THINK_PATH

定义框架路径,说的简单点就是将ThinkPHP这个核心目录相对于入口文件的相对路径定义给一个常量,方便书写且不容易写错,在以后要用到”./ThinkPHP/”地方用THINK_PATH代替之

define(“THINK_PATH”,”./ThinkPHP”);还有一个作用就是防跳墙!所谓防跳墙就是防止用户直接去访问敏感文件,通常做法是用一个非敏感页面去包含这个敏感页面,并且在非敏感页面做一些安全方面的策略,比如说进行验证,验证通过才允许访问其中的敏感页面!这实际上与通过一个public方法去访问一个private属性是一个道理的!

2、APP_PATH

准确的说应该叫做应用路径(APP->Application),而并非项目路径,虽然叫项目路径也没错,但是总是有些歧义!

·项目应该叫Project,这就是我们所开发的整个项目,ThinkPHP核心目录就是在项目下一级目录中

·应用即Application,这是我们所开发的整个项目中的一个个应用,比如说通常一个项目会有前台应用后台应用,那么就可以在这个项目的下一级目录中创建两个入口文件(说明一下,在没有进行分组时,单一入口文件的这个单一是针对应用来说的,所以如果有两个应用就需要两个入口文件),来分别创建这两个应用,即两个目录(与ThinkPHP核心目录平级)

3、APP_NAME

·让ThinkPHP在进行加载的时候更好的区分不同的应用

·便于以后在做权限管理(RBAC)的时候,能够更好的区分前台应用和后台应用的权限

4、引入”ThinkPHP.php”

注意,不要太纠结到底是哪个(include/include_once/require/require_once),重点是把”ThinkPHP.php”这个整个框架的入口文件引入!

5、APP::run();

用APP类的静态方法run()创建一个应用,并不是非得通过APP来调用这个静态方法,也可以$app=new APP;$app->run();这样通过一个APP对象来调用run()实现创建一个应用的操作

五、ThinkPHP的项目编译机制

项目编译机制的两个主要文件,~runtime.php(核心缓存文件)~app.php(项目编译缓存文件) ps,注意一下设置都是在入口文件中进行的

对于项目编译机制的理解,就是在第一次执行的时候,将执行所需要的部分系统代码和项目代码拎出来分别放 到~runtime.php和~app.php中,那么在以后的执行中,直接执行~runtime.php和~app.php中的代码,而不必向第一次执 行那样,需要去操作系统目录和项目目录中文件,从而节省了大量的I/O开销,那么项目的运行速度会有明显的提升!而且ThinkPHP 对~runtime.php和~app.php文件本身做了一定优化,即将这两个文件中的注释、空格、换行等等一些无关代码执行的字符去掉了,从而使得文 件更小,执行更加有效率!但是这并不利于程序员调试,因为这个代码就一行你怎么调试,所以要用 define(“STRIP_RUNTIME_SPACE”,false)加上这些注释、空格、换行等等字符,便于程序员阅读代码!

~runtime.php中的代码是由系统目录中的core.php文件决定的

~app.php文件通常包含项目配置文件(包括惯例配置(可以理解为默认配置)和项目配置(自定义配置),自定义配置优先级高于默认配置)、项目公用函数文件(common.php)

1、在测试define(‘STRIP_RUNTIME_SPACE’,false);发现的几个问题

·如果不删除Runtime目录,则它是不生效的,因为入口文件在创建整个应用目录的时候,如果发现要创建的目录存在,它是不会修改这个存在的目录的!

·无法直接删除应用目录(即使提供了系统管理员权限也不行,不知道咋地),但是可以删除应用目下的任何一个子目录

·这条语句必须是放在引入”ThinkPHP.php”的操作之前,否则是不起作用的!实际上,这对入口文件中的所有语句都适用

·这条语句对~app.php不起作用

2、define(“RUNTIME_PATH”,APP_PATH.”xxx/”);

  可以修改RUNTIME目录的,不过即使是修改RUMTIME目录,也要是在应用目录下,所以最好用APP_PATH拼一下

3、define(“NO_CACHE_RUNTIME”,True);

  进行该设置后,就不会生成核心(系统)缓存文件了,但是还是会生成项目(应用)编 译缓存文件,而在调试模式下不会生成项目编译缓存,但是会生成核心缓存,所以要想既不生成核心缓存也不生成项目编译缓存,就要同时开启调试模式和进行该设 置。在应用配置文件(应用目录->Config目录->Config.php文件)中的数组中添加一个元素”APP_Debug”=& gt;”True”即可开启调试模式

ThinkPHP教程_PHP框架之ThinkPHP(一)【入门和介绍、ThinkPHP版本和文件夹规范、项目入口文件】 – TigerYangWTH – 博客园已关闭评论
2016年8月19日 By mikel 分类: JavaScript

来源: 模拟AngularJS之依赖注入 – 猴子猿 – 博客园

一、概述

AngularJS有一经典之处就是依赖注入,对于什么是依赖注入,熟悉spring的同学应该都非常了解了,但,对于前端而言,还是比较新颖的。

依赖注入,简而言之,就是解除硬编码,达到解偶的目的。

下面,我们看看AngularJS中常用的实现方式。

方法一:推断式注入声明,假定参数名称就是依赖的名称。因此,它会在内部调用函数对象的toString()方法,分析并提取出函数参数列表,然后通过$injector将这些参数注入进对象实例。

如下:

复制代码
//方法一:推断式注入声明,假定参数名称就是依赖的名称。
//因此,它会在内部调用函数对象的toString()方法,分析并提取出函数参数列表,
//然后通过$injector将这些参数注入进对象实例
injector.invoke(function($http, $timeout){
    //TODO
});
复制代码

方法二:行内注入声明,允许我们在函数定义时,直接传入一个参数数组,数组包含了字符串和函数,其中,字符串代表依赖名,函数代表目标函数对象。

如下:

//方法二:行内注入声明,允许我们在函数定义时,直接传入一个参数数组,
//数组包含了字符串和函数,其中,字符串代表依赖名,函数代表目标函数对象。
module.controller('name', ['$http', '$timeout', function($http, $timeout){
    //TODO
}]);

看了上述代码,心中有一疑问,这些是怎么实现的呢?

哈哈,下面,我们就来一起模拟一下这些依赖注入方式,从而了解它们。

二、搭建基本骨架

依赖注入的获取过程就是,通过字段映射,从而获取到相应的方法。

故而,要实现一个基本的依赖注入,我们需要一个存储空间(dependencies),存储所需键值 (key/value);一个注册方法(register),用于新增键值对到存储空间中;还有一个就是核心实现方法(resolve),通过相关参数, 到存储空间中获得对应的映射结果。

So,基本骨架如下:

复制代码
var injector = {
    dependencies: {},
    register: function(key, value){
        this.dependencies[key] = value;
        return this;
    },
    resolve: function(){
                            
    }
};
复制代码
三、完善核心方法resolve

从我们搭建的基本骨架中,可以发现,重点其实resolve方法,用于实现我们具体形式的依赖注入需求。

首先,我们来实现,如下形式的依赖注入:推断式注入声明。

如下:

injector.resolve(function(Monkey, Dorie){
    Monkey();
    Dorie();
});

要实现上述效果,我们可以利用函数的toString()方法,我们可以将函数转换成字符串,从而通过正则表达式获得参数名,即key值。 然后通过key值,在存储空间dependencies找value值,没找到对应值,则报错。

实现如下:

复制代码
var injector = {
    dependencies: {},
    register: function(key, value){
        this.dependencies[key] = value;
        return this;
    },
    resolve: function(){
        var func, deps, args = [], scope = null;
        func = arguments[0];
        //获取函数的参数名
        deps = func.toString().match(/^function\s*[^\(]*\(\s*([^\)]*)\)/m)[1].replace(/ /g, '').split(',');
        scope = arguments[1] || {};
        for(var i = 0, len = deps.length; i < len, d = deps[i]; i++){
            if(this.dependencies[d]){
                args.push(this.dependencies[d]);
            }else{
                throw new Error('Can\'t find ' + d);
            }
        }
        func.apply(scope, args);                    
    }
};
复制代码

测试代码,如下:

代码稍长,请自行打开

推断式注入声明,有个缺点,就是不能利用压缩工具压缩,因为我们是通过函数的参数作为依赖的,当我们压缩时,会将参数名改掉,参数名都变了,那肯定扑街咯。

那么下面,我们就看看行内注入声明,它可以弥补这缺点。

实现行内注入声明,如下:

injector.resolve(['Monkey', 'Dorie', function(M, D){
    M();
    D();
}]);

利用typeof判断arguments[0]的类型可以辨别并获得依赖参数和函数。

实现如下:

复制代码
var injector = {
    dependencies: {},
    register: function(key, value){
        this.dependencies[key] = value;
        return this;
    },
    resolve: function(){
        var firstParams, func, deps = [], scope = null, args = [];
        firstParams = arguments[0];
        scope = arguments[1] || {};
        //获得依赖参数
        for(var i = 0, len = firstParams.length; i < len; i++){
            var val = firstParams[i],
                type = typeof val;
            if(type === 'string'){
                deps.push(val);
            }else if(type === 'function'){
                func = val;
            }
        }
        //通过依赖参数,找到关联值
        for(i = 0, len = deps.length; i < len, d = deps[i]; i++){
            if(this.dependencies[d]){
                args.push(this.dependencies[d]);
            }else{
                throw new Error('Can\'t find ' + d);
            }
        }
        func.apply(scope || {}, args);                    
    }
};
复制代码

测试代码,如下:

复制代码
<!DOCTYPE html>
    <head>
        <meta charset="utf-8"/>
    </head>
    <body>
        <script>
            var injector = {
                dependencies: {},
                register: function(key, value){
                    this.dependencies[key] = value;
                    return this;
                },
                resolve: function(){
                    var firstParams, func, deps = [], scope = null, args = [];
                    firstParams = arguments[0];
                    scope = arguments[1] || {};
                    //获得依赖参数
                    for(var i = 0, len = firstParams.length; i < len; i++){
                        var val = firstParams[i],
                            type = typeof val;
                        if(type === 'string'){
                            deps.push(val);
                        }else if(type === 'function'){
                            func = val;
                        }
                    }
                    //通过依赖参数,找到关联值
                    for(i = 0, len = deps.length; i < len, d = deps[i]; i++){
                        if(this.dependencies[d]){
                            args.push(this.dependencies[d]);
                        }else{
                            throw new Error('Can\'t find ' + d);
                        }
                    }
                    func.apply(scope || {}, args);                    
                }
            };
            //测试代码
            injector.register('Monkey', function(){
                console.log('Monkey');
            }).register('Dorie', function(){
                console.log('Dorie');
            });
            injector.resolve(['Monkey','Dorie',function(M, D){
                M();
                D();
                console.log('-.-');
            }]);    
        </script>
    </body>
</html>
复制代码

因为行内注入声明,是通过字符串的形式作为依赖参数,so,压缩也不怕咯。

最后,我们将上面实现的两种方法,整合到一起,就可以为所欲为啦。

那,就合并下吧,如下:

复制代码
var injector = {
    dependencies: {},
    register: function(key, value){
        this.dependencies[key] = value;
        return this;
    },
    resolve: function(){
        var firstParams, func, deps = [], scope = null, args = [];
        firstParams = arguments[0];
        scope = arguments[1] || {};
        //判断哪种形式的注入
        if(typeof firstParams === 'function'){
            func = firstParams;
            deps = func.toString().match(/^function\s*[^\(]*\(\s*([^\)]*)\)/m)[1].replace(/ /g, '').split(',');
        }else{
            for(var i = 0, len = firstParams.length; i < len; i++){
                var val = firstParams[i],
                    type = typeof val;
                if(type === 'string'){
                    deps.push(val);
                }else if(type === 'function'){
                    func = val;
                }
            }    
        }
        //通过依赖参数,找到关联值
        for(i = 0, len = deps.length; i < len, d = deps[i]; i++){
            if(this.dependencies[d]){
                args.push(this.dependencies[d]);
            }else{
                throw new Error('Can\'t find ' + d);
            }
        }
        func.apply(scope || {}, args);                    
    }
};
复制代码
四、花絮—RequireJS之依赖注入

依赖注入并非在AngularJS中有,倘若你使用过RequireJS,那么下面这种形式,不会陌生吧:

require(['Monkey', 'Dorie'], function(M, D){
    //TODO    
});

通过,上面我们一步步的模拟AngularJS依赖注入的实现,想必,看到这,你自己也会豁然开朗,换汤不换药嘛。

模拟实现如下:

复制代码
var injector = {
    dependencies: {},
    register: function(key, value){
        this.dependencies[key] = value;
        return this;
    },
    resolve: function(deps, func, scope){
        var args = [];
        for(var i = 0, len = deps.length; i < len, d = deps[i]; i++){
            if(this.dependencies[d]){
                args.push(this.dependencies[d]);
            }else{
                throw new Error('Can\'t resolve ' + d);
            }
        }
        func.apply(scope || {}, args);
    }
};
复制代码

测试代码如下:

复制代码
<!DOCTYPE html>
    <head>
        <meta charset="utf-8"/>
    </head>
    <body>
        <script>
            var injector = {
                dependencies: {},
                register: function(key, value){
                    this.dependencies[key] = value;
                    return this;
                },
                resolve: function(deps, func, scope){
                    var args = [];
                    for(var i = 0, len = deps.length; i < len, d = deps[i]; i++){
                        if(this.dependencies[d]){
                            args.push(this.dependencies[d]);
                        }else{
                            throw new Error('Can\'t resolve ' + d);
                        }
                    }
                    func.apply(scope || {}, args);
                }
            };
            //测试代码            
            injector.register('Monkey', function(){
                console.log('Monkey');
            }).register('Dorie', function(){
                console.log('Dorie');
            });
            injector.resolve(['Monkey', 'Dorie'], function(M, D){
                M();
                D();
                console.log('-.-');
            });        
        </script>
    </body>
</html>
复制代码
五、参考

1、AngularJS应用开发思维之3:依赖注入

2、Dependency injection in JavaScript

 

如果喜欢本文,请点击右下角的推荐,谢谢~
模拟AngularJS之依赖注入 – 猴子猿 – 博客园已关闭评论
2016年8月19日 By mikel 分类: JavaScript

来源: 微信公众号开发总结(Node.js+express+winston) – 智辉 – 博客园

转载请注明出处 http://www.cnblogs.com/laizhihui/p/5785125.html
依据:《中华人民共和国著作权法实施条例》《报刊转载、摘编法定许可付酬标准暂行规定》
保护原创,从你我做起。

关于订阅号、服务号、企业号

官方定位

订阅号:主要偏于为用户传达资讯(类似报纸杂志),认证后每天可以群发一条消息,可达到宣传效果,构建与读者之间更好的沟通和管理模式。
服务号:主要偏于服务交互(类似银行,114,提供服务查询),认证前后都是每个月可群发4条消息。给企业和组织提供更强大的业务服务与用户管理能力。
企业号:主要用于公司内部通讯使用,用来管理内部企业员工、团队。
订阅号、服务号、企业号的区别

接口权限区别

下图展示了接口权限的区别,虽然是官方的图,但不是时分准确了,详细请看 微信公众平台官网开发者文档

接口权限区别-不时分准确

微信认证

认证费用300元/次,需要年审,即300元/年

微信认证
详细请查看 官网资料

关于项目

环境搭建

  1. 安装 node.js,这里选择的是 v4.5.0 LTS,node.js 官网
  2. 安装 express$ npm install express -g
  3. 初始化项目,找到项目所要放置的文件夹,$ express WechatDemo$ cd WechatDemo$ npm install$ npm start,执行完以上命令,一个最基本的基于 express 的 node 项目已经搭建好了,此时访问 localhost:3000 应该能看到 express 初始化的页面。

项目调整为 MVC 结构

项目初始化时,目录是这样的:

项目初始化结构

项目最终目录结构:

项目最终目录结构

  1. 创建一个 app_server 文件夹,将根目录的 routes、views 移动到 app_server 文件夹下,再创建 controllers、dao、models 文件夹
  2. 此时如果启动项目,会报错,因为 app.js 还要相应修改如下,才能正常启动:
    var routes = require('./app_server/routes/index');
    var users = require('./app_server/routes/users');
    //...
    app.set('views', path.join(__dirname, 'app_server', 'views'));
  3. 由于 routes 中,路由与业务逻辑耦合在一起了,所以将路由文件中的内容移至 controllers 中, 在 controllers 中处理业务逻辑,详细可以查看源码
  4. 增加一个 dao 文件夹,操作数据库的逻辑写在这里。
  5. 增加一个 common 文件夹,用于存放公共的组件。

项目调试

  1. 为了不每次修改都手动重启服务器,这里采用 nodemon + WebStorm 2016.1.3 来调试项目,先安装 nodemon:$ npm install nodemon --save,save 是为了同步到 package.json。
  2. 在 WebStorm 中,点击右上角的下拉箭头,如果没有配置过,应该是灰色的,然后选择 Edit Configurations,选择弹出框左上角的 + 号,新增一个 Node.js 项,然后配置如下:
    WebStorm调试node.js
  3. 配置完成之后,点击右上角的 Debug 按钮(小虫形状的按钮)即可断点调试,修改 js 文件,也可以自动重启服务器了。

微信公众号接入

官网:微信公众平台开发者文档开始开发/接入指南 中有讲到如何接入。

用户与微信公众号交互时,主要有两种形式:

  1. 点击一个按钮,或者发送一段文字,这时,微信 app 会发送请求到微信服务器,微信服务器会转发请求到开发者的服务器。
  2. 点击一个链接,这时,微信 app 不会发送请求到微信服务器,而是直接请求链接所在的服务器。

对于第一种,为了安全起见(比如可能有心怀不轨的人故意给开发者的服务器发送请求,取消某人的关注,这时开发者的程序将该用户的openid移除, 此后该用户再也收不到公众号的消息,除非开发者主动刷新用户列表从微信服务器取回来。更甚者可能会对支付等比较敏感的操作进行模拟请求),微信服务器每次 发来的请求都带了加密的参数,通过特定的算法可以验证该请求来自微信服务器,算法在开发者文档里写得很清楚了,在接入认证的时候就需要用这种算法响应一次 微信服务端的请求。

这里采用微信官方开发的一个 npm 包 wechat 来处理请求,这意味着可以不写那套验证算法。$npm install wecaht --save,关于 wechat 的使用方法,请看官方文档,写得很详细。另外可以查看源码示例

另外,可以申请一个测试号,测试号几乎有所有的接口权限。

本地调试微信公众号

微信公众号接入的时候,是需要一台能够被微信服务器访问到的服务器,如果采用本地开发,localhost 不能被微信服务器访问到,那么就要想办法把本地服务器暴露出去,这里采用花生壳将 本地服务器映射到外网(注册时需要付费,我注册时是15元),但不是很稳定,偶尔映射会失败,需要重新启动软件或电脑。因为微信服务器需要接入到开发者服 务器的 80 端口或者 443 端口,如果这两个端口都被占用,可用花生壳将本地 3000 的端口映射为外网 80 端口,软件挺人性化的,一个按键即可映射到外网。

关于 access_token

开发者每次请求微信服务器提供的接口(除了获取access_token的接口),都需要在请求里带上 access_token 这个参数,但是这个参数的有效期只有 7200 秒,也就是两个小时,所以需要定期(setTimeout)去请求 access_token,但是如果服务器重启了,setTimeout 也就失效了,官方建议用一台独立的服务器专门获取 access_token,但如果不用独立的服务器,可以将 access_token 和 expired_in 保存到数据库。可以查看源码AccessTokenController.js 和 AccessTokenDao.js来了解详细的过程。

关于 log

  1. 在控制台、文件、MySQL 数据库记录 log
  2. 文件 log 每天记录在不同的文件
  3. 每个 request.on(“end”) 时记录 log
  4. 有需要时可以记录 log
  5. 格式为:2016-08-18 17:55:02 INFO NoticeDao.js [SQL:SELECT * FROM gdas_notice]

自定义 Logger API
logger.log 等级默认为 ‘info’,接收一个字符串或者一个对象,对象会被序列化成对应的格式
logger.info 与 logger.log 的作用是一样的
logger.warn level 为 warn
logger.error level 为 error

自定义 log 是在 winston 的基础上写出来的,winston 没有自带 MySQL log,可以自定义,详情请看 winston 官方文档
关于上面 log 的第 2 点,可以查看:winston-daily-rotate-file
自定义 log 也可以查看源代码

微信公众号开发总结(Node.js+express+winston) – 智辉 – 博客园已关闭评论
2016年8月18日 By mikel 分类: JavaScript

 

来源: 原生javascript实现分享到朋友圈功能 支持ios和android-JavaScript/Ajax教程-源码库|专注为中国站长提供免费商业网站源码下载!

  现在主流的分享工具也有很多,例如JiaThis、bShare分享,甚至一些大公司的如百度分享,但是他们依旧停留在只是在PC端的分享,对手机端的支持不是太好。

大家都知道现在很多手机端浏览器都内置了一些分享组件,像UC浏览器、QQ浏览器,内置的组件分享可以直接启动相应的APP分享,要是一个JS能调用浏览器的内置分享组件,那是多么酷啊。

高手在民间啊,下面ASPKU源码库的小编就带你来看看这位兄弟的编写的JS库。

一、工具介绍 nativeShare.js

一个可以通过JavaScript直接调用原生分享的工具,该工具具有以下特点:

1.支持原生微博、微信好友、微信Android/105260.html”>朋友圈、QQ好友、QQ空间分享
2.支持调用浏览器更多分享功能
3.不依赖任何JQuery以及其他插件
注意:目前仅支持手机UC浏览器和QQ浏览器

github项目地址:https://github.com/JefferyWang/nativeShare.js
Git@OSC项目地址:http://git.oschina.net/wangjunfeng/nativeShare.js

二、使用方法

1.引入CSS文件

 

复制代码代码如下:
<link rel=”stylesheet” href=”nativeShare.css”/>

 

2.在需要添加分享的地方插入以下代码:

 

复制代码代码如下:
<div id=”nativeShare”></div>

 

3.添加配置信息,并实例化

 

复制代码代码如下:
<script>
var config = {
url:’http://blog.wangjunfeng.com’,// 分享的网页链接
title:’王俊锋的个人博客’,// 标题
desc:’王俊锋的个人博客’,// 描述
img:’http://www.wangjunfeng.com/img/face.jpg’,// 图片
img_title:’王俊锋的个人博客’,// 图片标题
from:’王俊锋的博客’ // 来源
};
var share_obj = new nativeShare(‘nativeShare’,config);
</script>

 

三、演示截图

javascript,朋友圈,ios,android
图1:分享到新浪微博

javascript,朋友圈,ios,android
图2:分享到微信Android/105260.html”>朋友圈

javascript,朋友圈,ios,android
图3:微信分享给联系人

javascript,朋友圈,ios,android
图4:QQ分享给联系人

javascript,朋友圈,ios,android
图5:QQ分享到空间

javascript,朋友圈,ios,android
图6:调用浏览器的内置分享组件

注:相关教程知识阅读请移步到JavaScript/Ajax教程频道。

原生javascript实现分享到朋友圈功能 支持ios和android-JavaScript/Ajax教程-源码库|专注为中国站长提供免费商业网站源码下载!已关闭评论
2016年8月18日 By mikel 分类: JavaScript

让一个div层浮在最上层的方法_newevan_新浪博客,newevan,

来源: 让一个div层浮在最上层的方法_newevan_新浪博客

设置 style 中 z-index:auto
auto可定义为一个值(整数数字),越大代表越置前,如可定义为: z-index:9999。

若定义为-1,代表为最底层。

另:

若是会被一些FLASH文件给遮住,
可将flash对象的参数wmode设置为transparent即可。

让一个div层浮在最上层的方法_newevan_新浪博客已关闭评论
备案信息冀ICP 0007948