PHP实现执行定时任务的几种思路详解 - Web烤猫 - SegmentFault

mikel阅读(1011)

来源: PHP实现执行定时任务的几种思路详解 – Web烤猫 – SegmentFault

PHP本身是没有定时功能的,PHP也不能多线程。PHP的定时任务功能必须通过和其他工具结合才能实现,例如WordPress内置了wp-cron的功能,很厉害。本文,我们就来深入的解析几种常见的php定时任务的思路。

Linux服务器上使用CronTab定时执行php

我们先从相对比较复杂的服务器执行php谈起。服务器上安装了php,就可以执行php文件,无论是否安装了nginx或Apache这样的服务器环境软件。而Linux中,使用命令行,用CronTab来定时任务,又是绝佳的选择,而且也是效率最高的选择。

首先,进入命令行模式。作为服务器的linux一般都默认进入命令行模式的,当然,我们管理服务器也一般通过putty等工具远程连接到服务器,为了方便,我们用root用户登录。在命令行中键入:

crontab -e

之后就会打开一个文件,并且是非编辑状态,则是vi的编辑界面,通过敲键盘上的i,进入编辑模式,就可以编辑内容。这个文件中的每一行就是一个定时 任务,我们新建一行,就是新建一条定时任务(当然是指这一行内按照一定的格式进行书写)。我们现在来举个例子,增加一行,内容如下:

00 * * * * lynx -dump https://www.yourdomain.com/script.php

这是什么意思呢?实际上上面这一行由两部分组成,前面一部分是时间,后面一部分是操作内容。例如上面这个,

00 * * * *

就是指当当前时间的分钟数为00时,执行该定时任务。时间部分由5个时间参数组成,分别是:

分 时 日 月 周

第1列表示分钟1~59 每分钟用或者 */1表示,/n表示每n分钟,例如*/8就是每8分钟的意思,下面也是类推
第2列表示小时1~23(0表示0点)
第3列表示日期1~31
第4列表示月份1~12
第5列标识号星期0~6(0表示星期天)

整个句子的后面部分就是操作的具体内容。

lynx -dump https://www.yourdomain.com/script.php

意思就是说通过lynx访问这个url。我们在使用中主要用到lynx、curl、wget来实现对url的远程访问,而如果要提高效率,直接用php去执行本地php文件是最佳选择,例如:

00 */2 * * * /usr/local/bin/php /home/www/script.php

这条语句就可以在每2小时的0分钟,通过linux内部php环境执行script.php,注意,这里可不是通过url访问,通过服务器环境来执行哦,而是直接执行,因为绕过了服务器环境,所以效率当然要高很多。

好了,已经添加了几条需要的定时任务了吧。点击键盘上的Esc键,输入“:wq”回车,这样就保存了设置的定时任务,屏幕上也能看到提示创建了新的定时任务。接下来就是好好写你的script.php了。

关于CronTab的更多用法这里就不介绍了,如果你想更灵活的使用这个定时任务功能,应该自己再去深入学习一下crontab。

Windows服务器上使用bat定时执行php

windows上和linux上有一个类似的cmd和bat文件,bat文件类似于shell文件,执行这个bat文件,就相当于依次执行里面的命 令(当然,还可以通过逻辑来实现编程),所以,我们可以利用bat命令文件在windows服务器上面实现PHP定时任务。实际上在windows上定时 任务,和linux上道理是一样的,只不过方法和途径不同。好了下面开始。

首先,在一个你觉得比较适当的位置创建一个cron.bat文件,然后用文本编辑器打开它(记事本都可以),在里面写上这样的内容:

D:\php\php.exe -q D:\website\test.php

这句话的意思就是,使用php.exe去执行test.php这个php文件,和上面的contab一样,绕过了服务器环境,执行效率也比较高。写好之后,点击保存,关闭编辑器。

接下来就是设置定时任务来运行cron.bat。依次打开:“开始–>控制面板–>任务计划–>添加任务计划”,在打开的界面中 设置定时任务的时间、密码,通过选择,把cron.bat挂载进去。确定,这样一个定时任务就建立好了,在这个定时任务上右键,运行,这个定时任务就开始 执行了,到点时,就会运行cron.bat处理,cron.bat再去执行php。

非自有服务器(虚拟主机)上实现php定时任务

如果站长没有自己的服务器,而是租用虚拟主机,就无法进入服务器系统进行上述操作。这个时候应该如何进行php定时任务呢?其实方法又有多个。

使用ignore_user_abort(true)和sleep死循环

在一个php文档的开头直接来一句:

ignore_user_abort(true);

这时,通过url访问这个php的时候,即使用户把浏览器关掉(断开连接),php也会在服务器上继续执行。利用这个特性,我们可以实现非常牛的功能,也就是通过它来实现定时任务的激活,激活之后就随便它自己怎么办了,实际上就有点类似于后台任务。

而sleep(n)则是指当程序执行到这里时,暂时不往下执行,而是休息n秒钟。如果你访问这个php,就会发现页面起码要加载n秒钟。实际上,这种长时间等待的行为是比较消耗资源的,不能大量使用。

那么定时任务到底怎么实现呢?使用下面的代码即可实现:

<?php

ignore_user_abort(true);
set_time_limit(0);
date_default_timezone_set('PRC'); // 切换到中国的时间

$run_time = strtotime('+1 day'); // 定时任务第一次执行的时间是明天的这个时候
$interval = 3600*12; // 每12个小时执行一次

if(!file_exists(dirname(__FILE__).'/cron-run')) exit(); // 在目录下存放一个cron-run文件,如果这个文件不存在,说明已经在执行过程中了,该任务就不能再激活,执行第二次,否则这个文件被多次访问的话,服务器就要崩溃掉了

do {
  if(!file_exists(dirname(__FILE__).'/cron-switch')) break; // 如果不存在cron-switch这个文件,就停止执行,这是一个开关的作用
  $gmt_time = microtime(true); // 当前的运行时间,精确到0.0001秒
  $loop = isset($loop) && $loop ? $loop : $run_time - $gmt_time; // 这里处理是为了确定还要等多久才开始第一次执行任务,$loop就是要等多久才执行的时间间隔
  $loop = $loop > 0 ? $loop : 0;
  if(!$loop) break; // 如果循环的间隔为零,则停止
  sleep($loop); 
  // ...
  // 执行某些代码
  // ...
  @unlink(dirname(__FILE__).'/cron-run'); // 这里就是通过删除cron-run来告诉程序,这个定时任务已经在执行过程中,不能再执行一个新的同样的任务
  $loop = $interval;
} while(true);

通过执行上面这段php代码,即可实现定时任务,直到你删除cron-switch文件,这个任务才会停止。

但是有一个问题,也就是如果用户直接访问这个php,实际上没有任何作用,页面也会停在这个地方,一直处于加载状态,有没有一种办法可以消除这种影响呢?fsockopen帮我们解决了这个问题。

fsockopen可以实现在请求访问某个文件时,不必获得返回结果就继续往下执行程序,这是和curl通常用法不一样的地方,我们在使用curl 访问网页时,一定要等curl加载完网页后,才会执行curl后面的代码,虽然实际上curl也可以实现“非阻塞式”的请求,但是比fsockopen复 杂的多,所以我们优先选择fsockopen,fsockopen可以在规定的时间内,比如1秒钟以内,完成对访问路径发出请求,完成之后就不管这个路径 是否返回内容了,它的任务就到这里结束,可以继续往下执行程序了。利用这个特性,我们在正常的程序流中加入fsockopen,对上面我们创建的这个定时 任务php的地址发出请求,即可让定时任务在后台执行。如果上面这个php的url地址是www.yourdomain.com/script.php, 那么我们在编程中,可以这样:

// ...
// 正常的php执行程序
// ..

// 远程请求(不获取内容)函数,下面可以反复使用
function _sock($url) {
  $host = parse_url($url,PHP_URL_HOST);
  $port = parse_url($url,PHP_URL_PORT);
  $port = $port ? $port : 80;
  $scheme = parse_url($url,PHP_URL_SCHEME);
  $path = parse_url($url,PHP_URL_PATH);
  $query = parse_url($url,PHP_URL_QUERY);
  if($query) $path .= '?'.$query;
  if($scheme == 'https') {
    $host = 'ssl://'.$host;
  }

  $fp = fsockopen($host,$port,$error_code,$error_msg,1);
  if(!$fp) {
    return array('error_code' => $error_code,'error_msg' => $error_msg);
  }
  else {
    stream_set_blocking($fp,true);//开启了手册上说的非阻塞模式
    stream_set_timeout($fp,1);//设置超时
    $header = "GET $path HTTP/1.1\r\n";
    $header.="Host: $host\r\n";
    $header.="Connection: close\r\n\r\n";//长连接关闭
    fwrite($fp, $header);
    usleep(1000); // 这一句也是关键,如果没有这延时,可能在nginx服务器上就无法执行成功
    fclose($fp);
    return array('error_code' => 0);
  }
}

_sock('www.yourdomain.com/script.php');

// ...
// 继续执行其他动作
// ..

把这段代码加入到某个定时任务提交结果程序中,在设置好时间后,提交,然后执行上面这个代码,就可以激活该定时任务,而且对于提交的这个用户而言,没有任何页面上的堵塞感。

借用用户的访问行为来执行某些延迟任务

但是上面使用sleep来实现定时任务,是效率很低的一种方案。我们希望不要使用这种方式来执行,这样的话就可以解决效率问题。我们借用用户访问行 为来执行任务。用户对网站的访问其实是一个非常丰富的行为资源,包括搜索引擎蜘蛛对网站的访问,都可以算作这个类型。在用户访问网站时,内部加一个动作, 去检查任务列表中是否存在没有被执行的任务,如果存在,就将这个任务执行。对于用户而言,利用上面所说的fsockopen,根本感觉不到自己的访问竟然 还做出了这样的贡献。但是这种访问的缺点就是访问很不规律,比如你希望在凌晨2点执行某项任务,但是这个时间段非常倒霉,没有用户或任何行为到达你的网 站,直到早上6点才有一个新访问。这就导致你原本打算2点执行的任务,到6点才被执行。

这里涉及到一个定时任务列表,也就是说你需要有一个列表来记录所有任务的时间、执行什么内容。一般来说,很多系统会采用数据库来记录这些任务列表,比如wordpress就是这样做的。我则利用文件读写特性,提供了托管在github上的开源项目php-cron,你可以去看看。总之,如果你想要管理多个定时任务,靠上面的单个php是无法合理布局的,必须想办法构建一个schedules列表。由于这里面的逻辑比较复杂,就不再详细阐述,我们仅停留在思路层面上。

借用第三方定时任务跳板

很好玩的是,一些服务商提供了各种类型的定时任务,例如阿里云的ACE提供了单独的定时任务,你可以填写自己应用下的某个uri。百度云BCE提供 了服务器监测功能,每天会按照一定的时间规律访问应用下的固定uri。类似的第三方平台上还有很多定时任务可以用。你完全可以用这些第三方定时任务作为跳 板,为你的网站定时任务服务。比如说,你可以在阿里云ACE上建立一个每天凌晨2点的定时任务,执行的uri是/cron.php。然后你创建一个 cron.php,里面则采用fsockopen去访问你真正要执行某些任务的网站的url,例如上面的 www.yourdomain.com/script.php,而且在cron.php中还可以访问多个url。然后把cron.php上传到你的ACE 上面去,让ACE的定时任务去访问/cron.php,然后让cron.php去远程请求目标网站的定时任务脚本。

循环利用include包含文件(待验证)

php面向过程的特性使得其程序是从上往下执行的,利用这个特性,在我们使用include某个文件时,就会执行被引入的文件,知道include 的文件内程序执行完之后,再往下执行。如果我们创建一个循环,再利用sleep,不断的include某个文件,使循环执行某段程序,则可以达到定时执行 的目的。我们再进一步,并不是利用while(true)来实现循环,而是利用被include文件本身再include自身来实现循环,比如我们创建一 个do.php,它的内容如下:

if(...) exit(); // 通过某个开关来关闭执行

// ... 
// 执行某些程序
// ...

sleep($loop); // 这个$loop在include('do.php');之前赋值

include(dirname(__FILE__).'/do.php');

其实通过这种方法执行和while的思路也像。而且同样用到sleep,效率低。

PHP定时任务是一个非常有意思的东西,虽然说实话,用系统的php.exe去直接执行php文件的效率更高,但是对于很多普通站长而言,虚拟主机 是无法做到直接php执行原生程序的。本文仅提供一些解决的思路,我也仅仅是在学习中,有很多问题或表述都不正确,希望你指出来;你可以通过本文的思路, 开发出自己的一种解决方案,希望你能将方案发布,并与我一起探讨。

linux使用crontab实现PHP执行计划定时任务

mikel阅读(1711)

首先说说cron,它是一个linux下的定时执行工具。根用户以外的用户可以使用 crontab 工具来配置 cron 任务。所有用户定义的 crontab 都被保存在/var/spool/cron 目录中,并使用创建它们的用户身份来执行。要以某用户身份创建一个 crontab 项目,登录为该用户,然后键入 crontab -e 命令来编辑该用户的 crontab。该文件使用的格式和 /etc/crontab 相同。当对 crontab 所做的改变被保存后,该 crontab 文件就会根据该用户名被保存,并写入文件 /var/spool/cron/username 中。cron 守护进程每分钟都检查 /etc/crontab 文件、etc/cron.d/ 目录、以及 /var/spool/cron 目录中的改变。如果发现了改变,它们就会被载入内存。这样,当某个 crontab 文件改变后就不必重新启动守护进程了。

安装crontab:

yum install crontabs

说明:
/sbin/service crond start //启动服务
/sbin/service crond stop //关闭服务
/sbin/service crond restart //重启服务
/sbin/service crond reload //重新载入配置

查看crontab服务状态:service crond status

手动启动crontab服务:service crond start

查看crontab服务是否已设置为开机启动,执行命令:ntsysv

加入开机自动启动:
chkconfig –level 35 crond on

crontab命令:

功能说明:设置计时器。

语  法:crontab [-u <用户名称>][配置文件] 或 crontab [-u <用户名称>][-elr]

补充说明:cron是一个常驻服务,它提供计时器的功能,让用户在特定的时间得以执行预设的指令或程序。只要用户会编辑计时器的配置文件,就可以使 用计时器的功能。其配置文件格式如下:
Minute Hour Day Month DayOFWeek Command

参  数:
-e  编辑该用户的计时器设置。
-l  列出该用户的计时器设置。
-r  删除该用户的计时器设置。
-u<用户名称>  指定要设定计时器的用户名称。

crontab 格式:

基本格式 :

分钟   小时   日   月   星期   命令

*        *      *    *     *       *

第1列表示分钟1~59 每分钟用*或者 */1表示
第2列表示小时1~23(0表示0点)
第3列表示日期1~31
第4列 表示月份1~12
第5列标识号星期0~6(0表示星期天)
第6列要运行的命令

记住几个特殊符号的含义:
“*”代表取值范围内的数字,
“/”代表”每”,
“-”代表从某个数字到某个数字,
“,”分开几个离散的数字

# Use the hash sign to prefix a comment
# +—————- minute (0 – 59)
# | +————- hour (0 – 23)
# | | +———- day of month (1 – 31)
# | | | +——- month (1 – 12)
# | | | | +—- day of week (0 – 7) (Sunday=0 or 7)
# | | | | |
# * * * * * command to be executed

crontab几个例子如下:

(1)第一个例子。

30 21 * * * /etc/init.d/nginx restart
每晚的21:30重启 nginx。

(2)第二个例子,也就是本教程测试的例子

* * * * * /usr/bin/php -f /root/test.php >> test.log

每一分钟执行/root/test.php文件,将结果输出到test.log中。

完成了上面基础工作后,就来看看怎么使用crontab定时执行PHP脚本:

(1)我在/root下新建test.php文件,内容如下:

. 代码如下:
<?php
#!/usr/bin/php -q
echo  date(‘Y-m-d H:i:s’).”from http://www.phpddt.com \n”;
?>

说明:你可以用whereis php查找php执行文件位置。
(2)然后crontab -e编写如下shell:

. 代码如下:
* * * * * /usr/bin/php -f /root/test.php >> test.log

说明:test.php必须为可执行文件:chmod +x test.php

测试结果很正常,截图如下:

当然你可以用使用crontab -e继续添加任务,在/var/spool/cron下你可以看到一个root文件。
windows下直接用windows计划任务,通过bat打开网页就可以了。不像linux这么复制。

构建ASP.NET MVC5+EF6+EasyUI 1.4.5 +Unity4.x注入的后台管理系统(70)-微信公众平台开发-成为开发者 - ymnets - 博客园

mikel阅读(1937)

来源: 构建ASP.NET MVC5+EF6+EasyUI 1.4.5 +Unity4.x注入的后台管理系统(70)-微信公众平台开发-成为开发者 – ymnets – 博客园

前言:

一、阅读这段系列之前,你必须花半天时间大致阅读微信公众平台的API文档,我尽量以简短快速的语言与大家分享一个过程

二、借助微信公众平台SDK Senparc.Weixin for C#,所以你必须对Senparc进行独立的了解 http://weixin.senparc.com/

三、如果配置遇到困难,下载文章尾部源码进行参考

—————————————————资源———————————————————-

1.微信公众平台登录地址:https://mp.weixin.qq.com/

2.注册为公众账号后,会让你选择类型,类型分三种:订阅号(我是个人只能选择这个)、服务号、企业

三个类型接口开放程度不同,我们作为开发者,不必理会什么接口,只要成为开发者,全部高级接口都可以调用

3.登录后 成为开发者

4. SDK Senparc.Weixin封装了微信6.x的所有高级接口支持微信公众号、企业号、开放平台、微信支付、JSSDK

配置:

登录测试号后,可以看到一些配置,公开的接口,及说明,大致浏览所有接口名称之后。可以总结出:

1.开发环境利用appID,appsecret来获得Token

2.我们利用拿到的Token来访问提供的接口,如:修改菜单,发送信息

3.我们提供我们的服务器地址给微信,关注者通过微信服务器中转站访问我们的开发环境获得消息

开发环境:

VS2015+MVC5

知识点:

  1. 成为开发者
  2. 资源服务器

开始:

 1.登录微信公众号(左边菜单下边位置)

可以看到系统分配了一个AppID(应用ID)和

其他的需要我们手动填写服务器配置并启用开发者模式: URL地址,Token令牌,EncodingAESKey消息加密密钥

除了URL,我们都可以随便填写,那么URL是什么?

2.URL

图上的URL是我们的资源服务器,资源服务器是给微信中转的服务器,微信将对这个接口(我们的站点)进行Get和POST的请求。

查看官方接入文档,我们来创建一个资源服务器,尝试交互

https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421135319&token=&lang=zh_CN

开发者提交信息后,微信服务器将发送GET请求到填写的服务器地址URL上,GET请求携带参数如下表所示:

参数 描述
signature 微信加密签名,signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数。
timestamp 时间戳
nonce 随机数
echostr 随机字符串

 

开发者通过检验signature对请求进行校验(下面有校验方式)。若确认此次GET请求来自微信服务器,请原样返回echostr参数内容,则接入生效,成为开发者成功,否则接入失败。加密/校验流程如下:

1)将token、timestamp、nonce三个参数进行字典序排序

2)将三个参数字符串拼接成一个字符串进行sha1加密

3)开发者获得加密后的字符串可与signature对比,标识该请求来源于微信

3.创建资源服务器

1.新建一个ASP.NET MVC站点(不演示创建站点)

2.安装Senparc.Weixin.MP库

Install-Package Senparc.Weixin.MP

安装完成再再安装一个MVC的扩展包

Install-Package Senparc.Weixin.MP.MVC

3.新建一个WeChat控制器

控制器包含一个GET和Post的请求,Get是验证使用,Post是微信提交信息使用,比如关注者发送信息

复制代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Web;
using System.Web.Configuration;
using System.Web.Mvc;
using Senparc.Weixin.MP.Entities.Request;
using Senparc.Weixin.MP.MvcExtension;
using Senparc.Weixin.MP;
using Apps.Web.Areas.WC.Core;

namespace Apps.Web.Areas.WC.Controllers
{
    public class WeChatController : Controller
    {
        public static readonly string Token ="WeixinToken";//与微信公众账号后台的Token设置保持一致,区分大小写。
        public static readonly string EncodingAESKey = "dEq1BjMgmkEyOvva8pQfFwX95hBLOYKpAzBJ5y9pdSK";//与微信公众账号后台的EncodingAESKey设置保持一致,区分大小写。
        public static readonly string AppId = "wx3.......f5";//与微信公众账号后台的AppId设置保持一致,区分大小写。

        // GET: WC/WeChat
        public ActionResult Index()
        {
            return View();
        }

        [HttpGet]
        [ActionName("Index")]
        public Task<ActionResult> Get(string signature, string timestamp, string nonce, string echostr)
        {
            return Task.Factory.StartNew(() =>
            {
                if (CheckSignature.Check(signature, timestamp, nonce, Token))
                {
                    return echostr; //返回随机字符串则表示验证通过
                }
                else
                {
                    return "failed:" + signature + "," + CheckSignature.GetSignature(timestamp, nonce, Token) + "。" +
                        "如果你在浏览器中看到这句话,说明此地址可以被作为微信公众账号后台的Url,请注意保持Token一致。";
                }
            }).ContinueWith<ActionResult>(task => Content(task.Result));
        }


        /// <summary>
        /// 最简化的处理流程
        /// </summary>
        [HttpPost]
        [ActionName("Index")]
        public Task<ActionResult> Post(PostModel postModel)
        {
            return Task.Factory.StartNew<ActionResult>(() =>
            {
                if (!CheckSignature.Check(postModel.Signature, postModel.Timestamp, postModel.Nonce, Token))
                {
                    return new WeixinResult("参数错误!");
                }

                postModel.Token = Token;
                postModel.EncodingAESKey = EncodingAESKey; //根据自己后台的设置保持一致
                postModel.AppId = AppId; //根据自己后台的设置保持一致

                var messageHandler = new CustomMessageHandler(Request.InputStream, postModel, 10);

                messageHandler.Execute(); //执行微信处理过程

                return new FixWeixinBugWeixinResult(messageHandler);

            }).ContinueWith<ActionResult>(task => task.Result);
        }

    }
}
复制代码

(代码中的方法CustomMessageHandler)且看作者的解析:http://www.cnblogs.com/szw/p/3414862.html

我们先设置固定的三个值:

Token =”WeixinToken”;//与微信公众账号后台的Token设置保持一致,区分大小写。

EncodingAESKey = “dEq1BjMgmkEyOvva8pQfFwX95hBLOYKpAzBJ5y9pdSK”;//与微信公众账号后台的EncodingAESKey设置保持一致,区分大小写。

AppId = “wx3…….f5”;//与微信公众账号后台的AppId设置保持一致,区分大小写。

这几个值要和上面的相互对应起来才能!到此,我们的服务器资源就完成了,可以看到Senparc.Weixin SDK,帮我们完成很多很多的东西,我们根本不需要做什么。

4.发布站点

这次我们上一篇文章,环境准备就派上用场了,我们把刚刚新建的MVC发布到本地的IIS!然后利用内网穿透,获得外网访问

当你的运行与我一样时候,证明这个地址是可以作为资源服务器的,其实就是废话,我们从头到尾就只创建了一个控制器,加了如下代码,让控制器,能够支持一个GET和POST的Action方法而已。

5.发条微信试下(把服务器URL配回到微信公众号里面来)

把URL配置到微信里面

点击菜单:

点击开发者工具,拉到中间位置可以看到,这个测试公众号的二维码,用手机扫一下关注一下

关注之后给他发送一个信息!

6.服务器返回信息

如果发送信息后公众号提示:该公众号暂时无法提供服务,请稍后再试

那么要检查配置的APPID已经TOKEN是否一致,我刚开始就是因为配置错了,以为代码出错,调试了大半天!

(如果你扫我上面哪个二维码,那么你一定会得到红色那句话)

总结:

希望本文能给大家带来一些启发,我们开发者是如何与微信交流的。

同时也可见开源SDK Senparc的强大!什么都帮我们做好了,我们需要关心的只有我们的业务。

如果你在动手配置过程遇到什么困难,那么下载示例源码来进行参考

https://yunpan.cn/cMMwK2VAhMxS7  访问密码 b477

作者:YmNets
出处:http://ymnets.cnblogs.com/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

webApp 阅读器项目实践 - bluebird8880 - 博客园

mikel阅读(1692)

来源: webApp 阅读器项目实践 – bluebird8880 – 博客园

  这是一个webApp 阅读器的项目,是慕课网的老师讲授的一个实战,先给出项目源码在GitHub的地址:https://github.com/yulifromchina/MobileWebReader。

项目属于麻雀虽小,但五脏俱全的类型,对于前端新手来说,还是很有学习价值。

一、项目成果展示

二、项目所用技术

语言:Html,css,js

插件:

zepto.js: 使用于移动端的js库,语法与JQuery相似,但增加了触摸等移动端事件,去掉了对浏览器兼容的代码,因此更轻量级

JQuery.base64.js: 解码base64编码的插件

JQuery.jsonp.js: 提供jsonp请求的插件

 

三、项目实现功能分析

1、HTML结构

阅读器的构造主要是上边栏、文章主体、控制台面板、下边栏几部分:

 

2、静态页面交互

需要实现的功能有:

1)点击屏幕中央,出现上下边栏;再次点击,上下边栏消失(这是为什么在上一段代码中,添加了响应触摸屏幕的div这个结构,其宽度可以设置为屏幕宽度的30%左右)

2)点击字体,出现控制面板,面板可调节字体的大小;面板可设置内容主体的背景色

3)点击夜间/白天,背景色会切换成暗黑色/乳白色

 

对于以上功能,完成后,还需要在细节上进行优化:

1)用户滚动页面时,上下边栏应该消失,这样才能不阻碍用户阅读

2)调节字体大小时,应该有最大字体和最小字体的限制;

设置背景色时,选中的颜色的按钮,外围还有一个稍大一点的圆圈;

设置字体时,字体的图标应该高亮;

3)点击夜间/白天,这部分的功能实际上是背景色切换的子集,那么这部分的代码是可复用的;

 

完成了初步的优化后,还可以进一步完善:

1)用户设置的字体和背景色,是否可以缓存下来,使页面刷新时不变

 

3、和后台的交互

项目是在data文件夹放了一些json文件,模拟服务端的返回值,这里要解决的问题有:

1)项目中请求的数据是多看阅读中的数据,那么,如何跨域请求

2)获取到了数据后,如何更新到页面

 

四、项目技术点分析

1、HTML结构

1)控制面板部分,可以看到背景色的几个按钮是不透明的,而面板背景是半透明的:该效果可以通过设置两个div,第一个div设置为半透明的背景色,第二个div作为容纳按钮等标签的容器

 

2、静态页面交互

1)需要使用js事件来实现CSS样式的切换,包括点击和滚动;对于背景色的几个按钮,监听事件的方式都相同,这里可以使用事件委托的方式来减少代码

2)缓存背景色和字号,可以使用本地存储localStorage实现

 

3、和后台的交互

使用jsonp来实现跨域,获取模拟服务端返回的数据

关于Jsonp的理解:由于同源策略,a.html不能直接获取b.html的内容,因此利用<script>标签不受同源策略的 显示的特性,在script中请求b.html的内容;b.html并不直接返回内容,而是返回 callback({json数据}), 其中,callback是a.html传递给b.html的一个回调函数的名称;这样,b.html返回的实际是一段js代码;本地浏览器就会执行这段代 码。

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

mikel阅读(1496)

来源: 如何在 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 的第一部分内容,后续内容会在本系列的后两篇文章中呈现,敬请期待!

mysql UNIX时间戳与日期的相互转换 - 站长之家

mikel阅读(1416)

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’);

 

Android开发人员不得不收集的代码(不断更新中...) - /画家/ - 博客园

mikel阅读(711)

来源: 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;
}
复制代码

 

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

mikel阅读(795)

来源: 各种主流 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,软件整体表现较好,对我来说最重要的是在不购买的情况下也够用了,而且全中文的傻瓜式界面操作起来实在方便。

PHP中如何给日期加上一个月 加一周

mikel阅读(1218)

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 );

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

mikel阅读(936)

来源: 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”即可开启调试模式