[转载]《Troubleshooting SQL Server》读书笔记-内存管理 - Joe.TJ - 博客园

mikel阅读(1052)

[转载]《Troubleshooting SQL Server》读书笔记-内存管理 – Joe.TJ – 博客园.

自调整的数据库引擎(Self-tuning Database Engine)

长期以来,微软都致力于自调整(Self-Tuning)的SQL Server数据库引擎,用以降低产品的总拥有成本。从SQL Server 2005开始,SQL Server就是动态管理内存使用,并且调整内存使用时,不需要重启数据库引擎。

所以它也不提供内存分配的微调项。各个组件的内存分配,完全由数据库引擎自动管理,不能手动分配。但是这货还是提供了一些配置项,能够影响数据库引擎如何使用内存。

是否使用这些配置项来替代默认值,取决于操作系统版本,SQL Server版本,可用物理内存和处理器架构等。

 

SQL Server是怎么分配内存的

SQL Server本身设计就会尽可能多的使用内存。正常情况下,它不会释放已经分配的内存,除非OS引发并设定Low Memory资源通知标记(Resource Notification Flag)。

SQL Server 2005的SQLOS中添加了一个专用线程用于监控OS发出的内存通知(Memory Notification)(这也是自调整功能之一)。

OS中有两种类型的内存通知:

Memory High:SQL Server可以增加Working Set使用量并使用更多内存

Memory Low:OS有内存压力,SQL Server释放一些内存给OS

如果两种内存通知OS都没设定,则表明内存使用稳定,SQL Server将继续在现有的进程空间内运行。但是这个功能在Windows 2003和SQL Server 2005之前是没有的。

内存压力分类,根据Memory Pressure – Classified

image

SQL Server可以使用多少内存,取决于:

  • 服务器上安装的内存量
  • Windows系统的内存限制
  • SQL Server的架构(32bit/64bit)
  • SQL Server控制内存使用的配置项
  • SQL Server的版本

32位VAS的限制 

Windows在VAS中运行每一个进程。32位的进程最多只可寻址到4GB内存,而这4GB内存又分为内核模式(Kernel Mode)空间和用户模式(User Mode)空间。默认,windows会各分配2GB。

内核模式主要用于OS,用户模式用于当前执行的应用程序进程(例如SQL Server)。

1. 用户模式VAS分配和VirtualAlloc

SQL Server保留的2GB用户模式VAS,当出现物理内存分配时才会提交。它是通过VirtualAlloc这个Windows API。

32位的SQL Server或者Windows,调用VirtualAlloc返回一个32位的指针,这就是为什么SQL Server只能使用到2GB用户模式VAS的原因。

通过VirtualAlloc分配的内存并不一定是实际物理内存,当分配的内存被提交时,才会是RAM的内存。提交内存时,windows要确认SQL Server及其它应用程序进程提交的内存总量<=(RAM+分页文件)。

需要注意是VirtualAlloc分配的内存是可分页内存,意味着OS出现内存压力时,它们会被分页(page out)到磁盘上。

2. 非缓存池分配(MemToLeave)

SQL Server占用的大部分内存分配给了缓存池,用于缓存数据和查询计划。当需要大于8KB的连续页时,会通过多页分配器分配非缓存池,如LinkedServer,线程堆栈,CLR,备份缓存等。

为了确保有足够的非缓存池内存,32位SQL Server在启动时就会保留部分VAS。保留的部分也叫做MemToLeave,大小=MaxWorkerThread*0.5MB+256,其中 MaxWorkerThread=(ProcessorCount-4)+256.

默认情况下MemToLeave=256*0.5+256=384MB,所以缓存区的大小约为(2GB-384MB)=1664MB。

3. VAS调整(VAS Tuning)

在有4GB内存的服务器上,可以使用VAS调整使得用户模式VAS占到3GB,内核模式VAS减少为1GB.

需要注意的是内核模式内存的减少,使得系统PTEs(Page Table Entires)减少,造成系统不稳定,同时SQL Server可以寻址到的内存也变少了。

Windows 2008上实现VAS Tuning,使用BCDEdit /set IncreaseUserVa [value](value取2048到3072间的值)。

  4. AWE(Address windowing extension)

在多于4GB RAM的服务器上,可以使用AWE让SQL Server使用内存。使用AWE需先启用PAE,在windows 2008上使用BCDEdit /set PAE ForceEnable启用。

然后SQL Server开启”AWE Enabled”,服务账号需要具有锁定内存页的权限(Lock Pages In Memory,在组策略分配这个用户权限)。

AWE使内存管理的指针由32扩展到36位,所以最能寻址64GB内存。并且分配内存时,不使用VirtualAlloc而使用AllocateUserPhysicalPages函数。此API通过PTE直接分配物理内存。

AWE的内存只能被缓冲池(Buffer Pool)使用,并且是被锁定和不可分页的,所以最好使用设定“’max server memory”来限制一下量。

5. -g启动参数

32位平台上可以使用SQL Server的-g启动参数指定MemToLeave内存量,从而提高MemToLeave的内存分配量。但同时这也会减少缓存池的分配量。

 

使用64位的SQL Server

64位平台的VAS理论上限可达16EB=16,000,000TB,实际上X64限制在8TB,IA64为7TB。使用超过4GB RAM时,SQL Server不用进行额外配置。

SQL Server使用的内存只能通过VAS提交,所以所有内存都是非锁定的和可分页的。这样当OS有内存压力时,这些内存可能会被分页到磁盘(hard page out)。

VAS如此充足,MemToLeave的分配理论已经不再适用,同理-g启动参数也没有意义。

过程缓存(Procedure cache)也会存得更多,这可能会带来过程缓存过量的问题。

64位SQL Server的内存配置选项

1. 最小/大服务器内存

SQL Server提供了两个实际级别的,限制缓存池大小的配置项:min server memory/max server memory.需要注意的是从SQL Server 2000到2008 R2,这两个配置只对缓存池(Buffer pool)有效。

在启用了“锁定内存页”时,两都的差值意味着:当有外部内存压力时,SQL Server可以调整的范围。

设定最大值时,没有一个通用的值。初始化配置的基本原则:服务器内存<=16GB时,OS保留1GB,每4GB RAM保留1GB;>=16GB时,OS保留1GB,每8GB RAM保留1GB.

例如:32GB的服务器,最大值=32-1-4=27GB。然后确保性能计数器Memory\Available Mbytes介于150~300之间,逐渐调整max server memory。

2. 锁定内存页(lock pages in memory)

64位SQLOS默认使用VirtualAlloc分配所有的内存,此API分配的内存是非锁定和可分页的。当OS有内存压力时标记MemoryLow, SQL Server会释放内存直到”最小服务器内存”。

如果它释放的速度不够快或者释放的量不满足于OS,则这些内存会被分页到分页文件。对于使用大内存的SQL Server,WorkingSet分页对性能影响是非常严重的。

启用锁定内存页,使得SQL Server分配缓存池内存时使用AWE API AllocateUserPhysicalPages。此函数分配的内存是锁定的和不可分页的。

而缓存池占用着SQL Server大部分内存,所以启用锁定内存页会很大程度上避免WorkingSet分页。AWE Enabled配置项在64位SQL Server是无效的空操作。

启用锁定内存页后,任务管理器的SQLServr.exe显示的是非缓存池内存用量。需要使用SQL Server:Memory Manager\Total Server Memory查看总的内存用量。

启用锁定内存页是SQL Server 2005/2008/2008 R2企业版和2008 R2标准版的功能。在2008 SP1_CU2和2005 SP3_CU4更新后,也可以通过启用跟踪标记845来其它版本启用锁定内存页。

3. LPA(Large Page Alloction)

在X64系统上,大页分配是指使用2MB的大小分配内存页,默认内存页是4KB。启用LPA需要满足条件:a). SQL Server 企业版 b).服务器RAM>=8GB c).启用锁定内存页

X64系统上,启用LPS(Large Page Support)和跟踪标记834,SQL Server将使用大页分配缓存池内存,并且SQL Server的启动时间显著增长。

要严格测试性能受益情况,并尽量在SQL Server专用服务器上启用。

 

诊断内存压力

通过性能计数器和DMV来判断系统是否有在在内存压力。需要谨记的一条:通过一两个性能计数器,是不能确定任何系统压力的,要全面的分析。

SQL Server:Buffer Manager下的性能计数器

1. Buffer Cache Hit Ratio

建议值是OLTP>=95%,OLAP>=90.这个计数器本身并不能说明SQL Server有内存压力,>=95%只是说明了SQL Server按设计的那样执行了数据页的预读预取。

2. Page Life Expectancy

以秒为单位,代表高速缓存的页过期并所占空间被重用的时间。

3. Free Pages

SQL Server缓冲池中的空闲页数量。当Page Life Expectancy计数器持续下降,Free Pages接近0,Free List Stalls持续大小0,则是表示明显的内存压力。

4. Free List Stalls/Sec

每秒请求等待缓冲池中空闲页的次数。

5. Lazy Writes/sec

每秒被LazyWriter进程刷新的缓冲池数据页数量。发生Lazy Writes/sec的同时PLE和Free Pages较低,又发生Free List Stalls,则说明缺少RAM。

SQL Server:Memory Manager下的性能计数器

1. Total Server Memory (KB) 和Target Server Memory (KB)

前者表示SQL Server已经占用的内存量,后者表示SQL Server想要占用的内存量。后者大于前者时,证明SQL Server需要更多可用内存,也是内存压力标志之一。

2. Memory Grants Outstanding

成功获得workspace内存的进程总数。值太低表示有大量的用户活动或者负载过重,如果同时Memory Grants Pending值偏高,则也是内存压力的标志之一。

3. Memory Grants Pending

正在等待被授予workspace内存的进程总数。

内存相关的DMVs

sys.dm_exec_query_memory_grants,sys.dm_os_memory_cache_counters,sys.dm_os_sys_memory,sys.dm_os_memory_clerks

 

常见的内存相关的问题

1. SQL Server内存泄露的误区

SQL Server看起来部会吃掉服务器尽可能多的内存,这不是内存泄露。扩展存储过程或者链接服务器驱动的内存泄露,可能会导致SQL Server无限制地去获取内存。

2. 分页问题

从SQL Server 2005 SP2后,当SQL Server进程的WirkingSet被收缩并分页到磁盘上时,错误日志中会写入”a significant part of SQL Server process memory has been paged out.”

可能的原因有:

1.设定了不正确的最大服务器内存,并且未启用锁定内存页

2.Windows执行非缓冲的IO操作占用了大量的系统调整缓存,如拷贝文件

3.硬件驱动问题导致的内存过量使用或内存泄露

参考KB918483并找到进程工作集被收缩的根本原因并解决之。对于SQL Server而言启用锁定内存页,是取后也是解决此问题的唯一手段。

3. 启用了锁定内存页但未限制最大服务器内存导致OS不稳定

启用了锁定内存页但未限制最大服务器内存或者设定了过高的值,SQL Server会占尽可用内存,从而导致OS缺少内存而不稳定甚至崩溃。

启用了锁定内存页,一定要限制最大服务器内存,留下合适的内存量给OS使用。

4. 应用程序域标记为卸载导致内存压力(App Domain is marked for unload due to memory pressure)

这是SQLCLR相关的错误,通常在32位SQL Server上发生,也可能在设定了最大服务器内存,限制了SQLCLR的可用VAS的64位SQL Server上发生。

一般是由于SQLCLR程序集低效的内存使用方式和SQLCLR可用的VAS受限引起。

32位上发生此错误,建议升级到64位,以使用更多的用户模式VAS。但是如果因为SQLCLR程序集使用大内存对象(如DataSet)导致,升级可能也解决不了问题,

把SQLCLR代码做成独立的控制台或者WinForm程序并正确配置其内存使用。

如果SQLCLR存储整个执行的状态,但是代码访问安全性(Code Access Security)定义为UNSAFE,则卸载会丢失状态信息导致更严重的问题。

解决此问题最好是升级到64位,临时的解决方案是使用-g启动参数增加MemToLeave内存给SQLCLR使用。

5. 701错误和FAILED_VIRTUAL_RESERVE

当SQL Server分配一段连续的VAS区域失败时,就会报此错误并输出请求分配的大小。

通常这个错误只出现在MemToLeave受限的32位系统上,因为各种需要分配大于8KB的操作类型,如设定了过大备份缓存,XML,SQLCLR,空间数据类型和链接服务器等。

解决此问题最好是升级到64位,临时的解决方案是使用-g启动参数增加MemToLeave内存。

6. 过渡分配的虚拟机

今时今日,SQL Server虚拟化已经很常见了。虚拟化的管理器提供的高级功能“内存过载(Memory Overcommit)”允许虚拟机的内存总量超过宿主服务器实际RAM总量。

内存过载时,虚拟化管理器是通过内存释放(Memory Ballooning)和内存分页(Memory Paging)两种方法来处理内存压力的。

内存释放是一个运行在Guest VM中的进程。它是VM工具之一,通过释放驱动(Ballooning Driver)安装到每个Guest VM中。它可以从Guest获取内存返还给宿主。

当宿主有内存压力时(内存过载时),会通知内存释放返还内存,如果返还的内存量或者返还的速度不足以让宿主机解决内存压力,则宿主会直接把Guest VM的内存分页到磁盘。

被分页的Guest性能会下降,同样其上运行的SQL Server也受影响,甚至缓冲池内存会降到0.

现在被微软支持的虚拟化管理器都提供了方法设定Guest VM的最小保留内存用于保证上面的SQL Server正常运行,所以在虚拟化时要合理设定。

同时为每个SQL Server设定最小服务器内存,也可以一定程序避免此问题。

7. 多实例的内存设置

一台机器上多个实例,每一个都必需设定合适的最小和最大服务器内存,并且最大服务器内存之和要小于物理RAM以保留足够内存给OS使用。

如果不设定最小服务器内存,当OS或者其它实例有内存压力时,此实例会主动收缩内存而导致性能问题。

 

总结

最重要的是:今时今日你还有什么理由不用64位的系统!?

[转载]Android输入法框架分析(1)-三大组件 - 山水含清晖 - 博客园

mikel阅读(1096)

[转载]Android输入法框架分析(1)-三大组件 – 山水含清晖 – 博客园.

Android输入法框架(Input Method Framework,IMF)是Android中非常重要的模块,它分布于三个部分(确切的说,是三个进程),

  1. 包含编辑框的客户(Client)app,表示普通的使用输入法的app进程。当点击编辑框时,会切换出当前选中的输入法;当用户在输入法输入字 符,提交候选词,则会更新到编辑框中。为了完成这些行为,它需要跟下面的两个输入法相关服务进行交互。对于普通app开发者而言,他们一般使用系统提供的 EditText,该类和其父类TextView已经很好的封装了跟输入法服务之间的交互;如果是自定义的编辑框,则需要自己处理这种交互。
  2. 输入法(input method,IME)服务(service),是具体的输入法进程,例如自带的拉丁输入法或者谷歌,搜狗等拼音输入法。它们一般提供一个输入窗口,可以 根据用户的要求打开或者关闭;可以把用户输入的字符和提交的候选词更新给client等等。这是一个用户级别的Service。为了方便开发者编写新的输 入法,IMF提供了抽象基类InputMethodService供输入法开发者扩展。
  3. 输入法管理者(Input method manager,IMM)服务(Service),这是一个Android系统级的服务,用于管理多个输入法以及同其他系统服务(例如window manager service)进行交互。这部分代码是app开发者和IME开发者都不需要关心的。

为了方便描述,后文分别称该三个组件为:client,IME和IMM。

这三个部分需用共同合作才能完成输入法的工作。例如打开一个app,并且一个edit框获取了focus焦点。此时client会通知IMM打开输 入法,然后IMM查看当前选中的IME,并调用该IME的start操作。这个简单的开始操作需要三个组件的配合。再比如用户提交了候选词,此时IME需 要将候选词告诉client。这里须要IME和client的合作。

因为这三个部分是三个进程,所以它们之间必须通过IPC进行通讯。在Android中,IPC机制是通过binder机制和aidl接口进行通信的。

  1. 对于Client而言,它提供了两个接口IInputMethodClient.aidlIInputContext.aidl。前者是供IMM调用的,后者是供IME调用的。
  2. IMM提供了接口IInputMethodManager.aidl供其他两个组件调用。
  3. IME提供了两个接口IInputMethod.aidlIInputMethodSession.aidl,前者供IMM调用,后者供client直接调用。

这些调用关系可以参考下图:

这些接口定义都在java/com/android/internal/view目录下。那这些接口是如何实现的呢?

先看client提供的接口。IInputContext是由同一目录下的IInputConnectionWrapper实现的。正如名字所说,它只是一个wrapper,它把接收到的IPC消息委托给你InputConnection的一个实现。例如对于EditText而言,实现是EditableInputConneciton
在调用方,IME也不是直接操作IInputContext接口。它会调用实现了InputConnection接口的InputConnectionWrapper(也在前面目录下)。该对象封装了从client传过来的IInputContext实例。
对于IME对client的调用操作,它会经历下面流程(以调用commitText为例,它表示提交候选词):

  1. InputConnectionWrapper.commitText被IME进程中其他代码调用。
  2. 委托给IInputContext stub对象。
  3. 通过IPC跨进程传输
  4. IInputConnectionWrapper接受到该消息并调用其commitText处理。
  5. 如果当前在主UI线程,则直接嗲用InputConnection的实现(例如EditableInputConnection)的commitText方法;否则通过handler进行线程间通信。

在IME看来,接口是InputConnection;在client上,实现的也是InputConnection。IInputContext完全被隐藏起来了。所以Android官方文档说IME通过InputConnection接口来操作client。

再看client提供的另外一个接口IInputMethodClient,IMM是直接调用的。IMM的代码就是InputMethodManagerService。在client端,InputMethodManager类中有一个对IInputMethodClient.stub的实现。

对IMM提供的IInputMethodManager接口而言,它是由InputMethodManagerService来实现的。在 client端,InputMethodManager的getInstance(是个singleton)会调用 ServiceManager.getService(Context.INPUT_METHOD_SERVICE)获取该接口,然后创建 InputMethodManager。所以对于client而言,它跟IMM的交互都是通过InputMethodManager来封装完成的,并不需 要关心IInputMethodManager接口。对于IME,如果它想操作IMM,也同样通过InputMethodManager。

下面是IME提供的接口。类似于使用InputConnection封装IInputContext,有两个接口InputMethodInputMethodSession分别对应着了IInputMethod和IInputSession。
对于InputMethod,IInputMethodWrapper实现了IInputMethod.stub。对于收的的IPC请求,都转发给InputMethod实例。一般而言,这个实例是InputMethodService中 定义的InputMethodImpl。该实例是InputMethodService的内部类,所以可以操作InputMethodService。对 于其客户IMM,InputMethodManagerService会直接调用IInputMethod的方法发起IPC请求。
对于InputMethodSessoin,非常类似,IInputMethodSessionWrapper实 现了IInputMethodSession.stub。同样在InputMethodService中有InputMethodSessionImpl 实现了InputMethodSession接口,有一个该类型的对象在IInputMethodSessionWrapper中,负责具体处理过来的 IPC消息。在client端,InputMethodManager有一个IInputMethodSession mCurMethod对象。开发者只需要调用InputMethodManager,而由InputMethodManager调用 IInputMethodSession的IPC操作。

总结一下,无论是client还是IME的开发者,都不需要直接操作aidl接口。在client端,对于IMM和IME的操作都是通过 InputMethodManager发起的,用户甚至不用关心这些IPC操作是发给谁的;在IME端,开发者通过InputConnection给 client发IPC消息,通过InputMethodManager给IMM发。而在IMM端,虽然是直接操作aidl接口的stub对象,但因为一般 开发者不需要改写它,所以也无关紧要。通过这种方式,简化了开发者的跨进程操作。

最后再总结下代码位置:

代码主要就是这四个目录。

[转载]C#中Trim、TrimStart、TrimEnd的用法 - 猴子哥 - 博客园

mikel阅读(904)

转载C#中Trim、TrimStart、TrimEnd的用法 – 猴子哥 – 博客园.

    这三个方法用于删除字符串头尾出现的某些字符。Trim()删除字符串头部及尾部出现的空格,删除的过程为从外到内,直到碰到一个非空格的字符为止,所以 不管前后有多少个连续的空格都会被删除掉。TrimStart()只删除字符串的头部的空格。TrimEnd()只删除字符串尾部的空格。

      如果这三个函数带上字符型数组的参数,则是删除字符型数组中出现的任意字符。如Trim(“abcd”.ToCharArray())就是删除字符串头部及尾部出现的a或b或c或d字符,删除的过程直到碰到一个既不是a也不是b也不是c也不是d的字符才结束。
这里最容易引起的误会就是以为删除的是”abcd”字符串。如下例:
string s = ” from dual union all “;
s = s.Trim().TrimEnd(“union all”.ToCharArray());
可能有人以为上面s的最终结果是”from dual”,但真正的结果是”from d”。需要注意的是这种写法执行的删除对象是字符数组中出现的任意字符,而不是这些字符连在一起组成的字符串! 

一般TRIM函数用法:
Trim()   功能删除字符串首部和尾部的空格。   语法Trim ( string )   参数string:string类型,指定要删除首部和尾部空格的字符串返回值String。函数执行成功时返回删除了string字符串首部和尾部 空格的字符串,发生错误时返回空字符串(””)。如果任何参数的值为NULL,Trim()函数返回NULL。    ========================================================================   SQL 中的 TRIM 函数是用来移除掉一个字串中的字头或字尾。最常见的用途是移除字首或字尾的空白。这个函数在不同的资料库中有不同的名称:  

 MySQL: TRIM(), RTRIM(), LTRIM()   Oracle: RTRIM(), LTRIM()  

 SQL Server: RTRIM(), LTRIM()   

各种 trim 函数的语法如下:

 TRIM([[位置] [要移除的字串] FROM ] 字串): [位置] 的可能值为 LEADING (起头), TRAILING (结尾), or BOTH (起头及结尾)。 这个函数将把 [要移除的字串] 从字串的起头、结尾,或是起头及结尾移除。如果我们没有列出 [要移除的字串] 是什么的话,那空白就会被移除。   LTRIM(字串): 将所有字串起头的空白移除。   RTRIM(字串): 将所有字串结尾的空白移除。

[转载]iOS- 如何集成支付宝 - 清澈Saup - 博客园

mikel阅读(1964)

[转载]iOS- 如何集成支付宝 – 清澈Saup – 博客园.

现在不少app内都集成了支付宝功能
使用支付宝进行一个完整的支付功能,大致有以下步骤:
1>先与支付宝签约,获得商户ID(partner)和账号ID(seller)
(这个主要是公司的负责)
2>下载相应的公钥私钥文件(加密签名用)
3>下载支付宝SDK(登录网站http://club.alipay.com/
里面提供了非常详细的文档、如何签约、如何获得公钥私钥、如何调用支付接口。
4>生成订单信息
5>调用支付宝客户端,由支付宝客户端跟支付宝安全服务器打交道
6>支付完毕后返回支付结果给商户客户端和服务器
SDK里有集成支付宝功能的一个Demo>  集成支付功能的具体操作方式,可以参考Demo
当第一次打开Demo时,可能会出现以下问题:
错误原因很简单,就是项目的部署版本设置太低了,从3.0改为4.3即可
要想集成支付功能,依赖以下文件夹的库文件(把这3个添加到你的客户端中)
调用支付接口可以参考AlixPayDemoViewController的下面方法
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath

如何创建订单 ( 订单根据自己公司看是什么样的)

如何签名

如何调用支付接口

都在这个方法里面了

//
//选中商品调用支付宝快捷支付
//
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    /*
     *点击获取prodcut实例并初始化订单信息
     */
    Product *product = [_products objectAtIndex:indexPath.row];
    
    /*
     *商户的唯一的parnter和seller。
     *本demo将parnter和seller信息存于(AlixPayDemo-Info.plist)中,外部商户可以考虑存于服务端或本地其他地方。
     *签约后,支付宝会为每个商户分配一个唯一的 parnter 和 seller。
     */
    //如果partner和seller数据存于其他位置,请改写下面两行代码
    NSString *partner = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"Partner"];
    NSString *seller = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"Seller"];
    
    //partner和seller获取失败,提示
    if ([partner length] == 0 || [seller length] == 0)
    {
        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"提示"
                                                        message:@"缺少partner或者seller。" 
                                                       delegate:self 
                                              cancelButtonTitle:@"确定" 
                                              otherButtonTitles:nil];
        [alert show];
        [alert release];
        return;
    }
    
    /*
     *生成订单信息及签名
     *由于demo的局限性,本demo中的公私钥存放在AlixPayDemo-Info.plist中,外部商户可以存放在服务端或本地其他地方。
     */
    //将商品信息赋予AlixPayOrder的成员变量
    AlixPayOrder *order = [[AlixPayOrder alloc] init];
    order.partner = partner;
    order.seller = seller;
    order.tradeNO = [self generateTradeNO]; //订单ID(由商家自行制定)
    order.productName = product.subject; //商品标题
    order.productDescription = product.body; //商品描述
    order.amount = [NSString stringWithFormat:@"%.2f",product.price]; //商品价格
    order.notifyURL =  @"http://www.xxx.com"; //回调URL
    
    //应用注册scheme,在AlixPayDemo-Info.plist定义URL types,用于快捷支付成功后重新唤起商户应用
    NSString *appScheme = @"AlixPayDemo"; 
    
    //将商品信息拼接成字符串
    NSString *orderSpec = [order description];
    NSLog(@"orderSpec = %@",orderSpec);
    
    //获取私钥并将商户信息签名,外部商户可以根据情况存放私钥和签名,只需要遵循RSA签名规范,并将签名字符串base64编码和UrlEncode
    id<DataSigner> signer = CreateRSADataSigner([[NSBundle mainBundle] objectForInfoDictionaryKey:@"RSA private key"]);
    NSString *signedString = [signer signString:orderSpec];
    
    //将签名成功字符串格式化为订单字符串,请严格按照该格式
    NSString *orderString = nil;
    if (signedString != nil) {
        orderString = [NSString stringWithFormat:@"%@&sign=\"%@\"&sign_type=\"%@\"",
                                 orderSpec, signedString, @"RSA"];
        
        //获取快捷支付单例并调用快捷支付接口
        AlixPay * alixpay = [AlixPay shared];
        int ret = [alixpay pay:orderString applicationScheme:appScheme];
        
        if (ret == kSPErrorAlipayClientNotInstalled) {
            UIAlertView * alertView = [[UIAlertView alloc] initWithTitle:@"提示" 
                                                                 message:@"您还没有安装支付宝快捷支付,请先安装。" 
                                                                delegate:self 
                                                       cancelButtonTitle:@"确定" 
                                                       otherButtonTitles:nil];
            [alertView setTag:123];
            [alertView show];
            [alertView release];
        }
        else if (ret == kSPErrorSignError) {
            NSLog(@"签名错误!");
        }

    }

    [tableView deselectRowAtIndexPath:indexPath animated:YES];
}

主要集成的关键就是下面几步:

//.封装订单模型
AlixPayOrder *order = [[AlixPayOrder alloc] init];
// 生成订单描述
NSString *orderSpec = [order description];

//2.签名
id<DataSigner> signer = CreateRSADataSigner(@“私钥key”);
// 传入订单描述 进行 签名
NSString *signedString = [signer signString:orderSpec];


//3.生成订单字符串
NSString *orderString = [NSString stringWithFormat:@"%@&sign=\"%@\"&sign_type=\"%@\"",
                         orderSpec, signedString, @"RSA"];

//4.调用支付接口
AlixPay * alixpay = [AlixPay shared];
// appScheme:商户自己的协议头
int ret = [alixpay pay:orderString applicationScheme:appScheme];

[转载]跨服务器连接查询(补充) - select left('claro',2)的专栏 - 博客频道 - CSDN.NET

mikel阅读(1170)

[转载]跨服务器连接查询(补充) – select left(‘claro’,2)的专栏 – 博客频道 – CSDN.NET.

如何方便的建立远程链接服务器   的文章中说明如何在SQL2005环境下方便快捷有效的建立跨服务器查询;

 

-- 很多朋友问局域网多数据库服务器访问该如何操作?下面简单说明如何配置远程链接访问。

/*********** 环境说明 ***********/

-- 源机器 IP 为 10. 0.0.211 ,在该机器所在数据库建立与目标机器的远程数据库链接。

-- 目标机器 IP 为 10. 0.0.222 。

-- 步骤(一)是指直接用 IP 进行远程链接,个人觉得用 IP 链接虽然麻烦,但是直观而且在多服务器操作时不会出现误链接的情况。

-- 步骤(二)是指直接用别名进行远程链接,较方便;但在高压力,大脑暂时短路情况下容易出现误链接数据库情况发生。

 

/*********** 步骤(一) ***********/

-- 建立连接服务器

EXEC sp_addlinkedserver   '10.0.0.222' , 'SQL Server'

 

/*-- 如果数据库有架构名需要做远程登录之间的映射

-- 创建链接服务器上远程登录之间的映射

EXEC sp_addlinkedsrvlogin '10.0.0.222','false','SA',' 架构名 ',' 登录密码 '

*/

-- 查询数据

-- 含架构名

select top 10 * from [10.0.0.222]. 数据库名 . 架构名 . 表名

-- 不含架构名

select top 10 * from [10.0.0.222]. 数据库名 . dbo. 表名

 

 

-- 查看链接服务器信息

select name , product, provider, data_source, query_timeout, lazy_schema_validation, is_remote_login_enabled, is_rpc_out_enabled

  from sys.servers

where is_linked= 1

 

/*********** 建立步骤(二) ***********/

/*-- 如果用 IP 连接觉得麻烦,可以新建别名

-- 配置链接服务器属性

exec sp_serveroption '222','name','10.0.0.222'

*/

-- 查询数据

-- 含架构名

select top 10 * from 222. 数据库名 . 架构名 . 表名

-- 不含架构名

select top 10 * from 222. 数据库名 . dbo. 表名

 

 

-- 删除链接服务器登录名映射

exec sp_droplinkedsrvlogin '10.0.0.222' ,NULL

 

-- 删除链接服务器属性

exec sp_dropserver '222'

 

--注:如果执行 删除链接服务器时提示如下错误时,

消息 15190,级别 16,状态 1,过程 sp_dropserver,第 56 行
仍有对服务器 'XXXX' 的远程登录或链接登录。

执行

exec sp_dropserver ' 链接服务器 ' , 'droplogins' 

SQL2008和SQL2000可以跨服务器连接查询的测试实例   中说明如何在SQL2000和SQL2008环境下跨服务器的查询。

 

以上跨服务器查询都是在先建立链接服务器的基础上,

如果不建立链接服务器可以直接用SQL查询跨服务器的Table吗?

如果不建立链接服务器可以查询跨服务器的系统表吗?

 

当然可以!

SQL2008和SQL2000可以跨服务器连接查询的测试实例 中的各实例对应的sa如何不相同,建立链接服务器后仍无法查询系统表,所以这里强调可以查询跨服务器的系统表并告诉使用它的目的。

 

当然,我们都熟悉opendatasource的使用,这里也不例外。

环境:

目标服务器SERV1,IP地址10.0.0.250,数据库INTER,架构名DBO

目的:

在本地服务器查询目标服务器上的INTER数据库的表logdb。

 

脚本一:

查询服务器SERV1上INTER数据库的表logdb并返回前十行

select top 10 *

from opendatasource ( ‘SQLOLEDB’

, ‘Data Source=10.0.0.250;User ID=sa;PASSWORD=sa 密码 ;’ )   输入目标服务器地址(原因请见《如何方便的建立远程链接服务器》)

输入 sa 帐号及密码

. [INTER] . dbo . logdb

  如果结果 返回类似如下结果:

链接服务器”(null)”的 OLE DB 访问接口 “SQLNCLI10” 返回了消息 “登录超时已过期”。
链接服务器”(null)”的 OLE DB 访问接口 “SQLNCLI10” 返回了消息 “与 SQL Server 建立连接时发生了与网络相关的或特定于实例的错误。找不到或无法访问服务器。请检查实例名称是否正确以及 SQL Server 是否配置为允许远程连接。有关详细信息,请参阅 SQL Server 联机丛书。”。
消息 53,级别 16,状态 1,第 0 行
命名管道提供程序: 无法打开与 SQL Server 的连接 [53].

  请检查并确认输入的目标服务器地址及SA密码是否正确!

 

如果结果 返回类似如下结果:

 

消息 7314 ,级别 16 ,状态 1 ,第 1

链接服务器 “(null)” OLE DB 访问接口 “SQLNCLI10” 不包含表 “”INTERFACEDATA”.”dbo”.”logdb”” 。该表不存在,或者当前用户没有访问该表的权限。

  请检查并确认输入的表名是否正确!

 

 

 

脚本二:

这里我们假设输入的表名是错的,但不记得正确的表名是什么。此时通常我们会查询sys.objects是否存在该表并确认名称。这里也不例外:

查询服务器SERV1上INTER数据库中是否存在表 db log并返回正确的表名

select NAME

from opendatasource ( ‘SQLOLEDB’

, ‘Data Source=10.0.0.250;User ID=sa;PASSWORD=sa 密码 ;’ )

. [INTER] . [SYS] . [OBJECTS] 查询目标服务器的系统表

where TYPE = ‘U’ and NAME LIKE ‘%log’ 查询用户表以 “log” 结尾的表名

ORDER BY 1  NAME 排序

 

 

 

Posted by: select left(‘claro’,2) @10:27:00

lable: SQL

[转载]android获取web服务器端session并验证登陆 - 云忠飞鸽 - 博客频道 - CSDN.NET

mikel阅读(1044)

[转载]android获取web服务器端session并验证登陆 – 云忠飞鸽 – 博客频道 – CSDN.NET.

传统网页实现用户登陆一般采用session或cookie记录用户基本信息又或者两者结合起来使用。android也 可以采用session实现用户登陆验证并记录用户登陆状态时的基本信息,session是在服务器端的;而类似cookie的记录方式,则可以在客户端 采用xml文件记录用户基本信息,重要数据则可以加密存放客户端。Android实现的session登陆功能与网页请求不同的是,网页形式的一次成功的 登陆请求后,再点击其他页面时,session一直是存在的,在一定时间内是有效的;而采用Android客户端请求的一次成功登陆后,再次发送新的请 求,则会产生新的session,而不是原来的。这就需要记录session的id号,并在整个请求过程中都记录并传递这个id号,才能保证 session的一致性。

以获取php session为例,主要思路实现分为客户端与服务器端3个步骤。

附件:  GetWebSession.zip (71.44 KB, 下载次数: 63) 

2012-5-8 14:09 上传

点击文件名下载附件
下载积分: 下载豆 -1

1.)客户端(Android

建立一个名为GetWebSession的Android项目,编写GetWebSession.java,LoginSuccessActivity.java,GetUserInfoActivity.java三个activity类。

 

1. GetWebSession.java主要是实现布局界面以及发送用户名和密码到php服务器端验证,如果验证成功则跳转到 LoginSuccessActivity.java类。GetWebSession.java主要涉及到与服务器端连接请求,对从服务器端返回的 json数据(如用户id,session等)进行解析,并存入HashMap,传递到LoginSuccessActivity.java

 

代码如下:

 

  1. package com.login.main;
  2. import java.io.IOException;
  3. import java.io.UnsupportedEncodingException;
  4. import java.util.ArrayList;
  5. import java.util.HashMap;
  6. import java.util.List;
  7. import org.apache.http.HttpEntity;
  8. import org.apache.http.HttpResponse;
  9. import org.apache.http.client.ClientProtocolException;
  10. import org.apache.http.client.entity.UrlEncodedFormEntity;
  11. import org.apache.http.client.methods.HttpPost;
  12. import org.apache.http.impl.client.DefaultHttpClient;
  13. import org.apache.http.message.BasicNameValuePair;
  14. import org.apache.http.protocol.HTTP;
  15. import org.apache.http.util.EntityUtils;
  16. import org.json.JSONException;
  17. import org.json.JSONObject;
  18. import android.app.Activity;
  19. import android.content.Context;
  20. import android.content.Intent;
  21. import android.os.Bundle;
  22. import android.view.View;
  23. import android.view.View.OnClickListener;
  24. import android.widget.Button;
  25. import android.widget.EditText;
  26. import android.widget.Toast;
  27. public class GetWebSession extends Activity {
  28. /** Called when the activity is first created. */
  29. private EditText user;
  30. private EditText password;
  31. private Button loginBtn;
  32. private Button logoutBtn;
  33. //主要是记录用户会话过程中的一些用户的基本信息
  34. private HashMap<String, String> session =new HashMap<String, String>();
  35. @Override
  36. public void onCreate(Bundle savedInstanceState) {
  37. super.onCreate(savedInstanceState);
  38. setContentView(R.layout.main);
  39. user=(EditText)findViewById(R.id.user);
  40. password=(EditText)findViewById(R.id.password);
  41. loginBtn=(Button)findViewById(R.id.loginBtn);
  42. loginBtn.setOnClickListener(loginClick);
  43. logoutBtn=(Button)findViewById(R.id.logoutBtn);
  44. logoutBtn.setOnClickListener(logoutClick);
  45. }
  46. OnClickListener loginClick=new OnClickListener() {
  47. @Override
  48. public void onClick(View v) {
  49. // TODO Auto-generated method stub
  50. if(checkUser()){
  51. Toast.makeText(v.getContext(), “用户登录成功!”, Toast.LENGTH_SHORT).show();
  52. Context context = v.getContext();
  53. Intent intent = new Intent(context,
  54. LoginSuccessActivity.class);
  55. //传递session参数,在用户登录成功后为session初始化赋值,即传递HashMap的值
  56. Bundle map = new Bundle();
  57. map.putSerializable(“sessionid”, session);
  58. intent.putExtra(“session”, map);
  59. context.startActivity(intent); // 跳转到成功页面
  60. }
  61. else
  62. Toast.makeText(v.getContext(), “用户验证失败!”, Toast.LENGTH_SHORT).show();
  63. }
  64. };
  65. OnClickListener logoutClick=new OnClickListener() {
  66. @Override
  67. public void onClick(View v) {
  68. // TODO Auto-generated method stub
  69. System.exit(0);
  70. }
  71. };
  72. private boolean checkUser(){
  73. String username=user.getText().toString();
  74. String pass=password.getText().toString();
  75. DefaultHttpClient mHttpClient = new DefaultHttpClient();
  76. HttpPost mPost = new HttpPost(“http://10.0.2.2/web/php/login.php”);
  77. //传递用户名和密码相当于
  78. //http://10.0.2.2/web/php/login.php?username=”&password=”
  79. List<BasicNameValuePair> pairs = new ArrayList<BasicNameValuePair>();
  80. pairs.add(new BasicNameValuePair(“username”, username));
  81. pairs.add(new BasicNameValuePair(“password”, pass));
  82. try {
  83. mPost.setEntity(new UrlEncodedFormEntity(pairs, HTTP.UTF_8));
  84. } catch (UnsupportedEncodingException e) {
  85. // TODO Auto-generated catch block
  86. e.printStackTrace();
  87. }
  88. try {
  89. HttpResponse response = mHttpClient.execute(mPost);
  90. int res = response.getStatusLine().getStatusCode();
  91. if (res == 200) {
  92. HttpEntity entity = response.getEntity();
  93. if (entity != null) {
  94. String info = EntityUtils.toString(entity);
  95. System.out.println(“info———–“+info);
  96. //以下主要是对服务器端返回的数据进行解析
  97. JSONObject jsonObject=null;
  98. //flag为登录成功与否的标记,从服务器端返回的数据
  99. String flag=””;
  100. String name=””;
  101. String userid=””;
  102. String sessionid=””;
  103. try {
  104. jsonObject = new JSONObject(info);
  105. flag = jsonObject.getString(“flag”);
  106. name = jsonObject.getString(“name”);
  107. userid = jsonObject.getString(“userid”);
  108. sessionid = jsonObject.getString(“sessionid”);
  109. } catch (JSONException e) {
  110. // TODO Auto-generated catch block
  111. e.printStackTrace();
  112. }
  113. //根据服务器端返回的标记,判断服务端端验证是否成功
  114. if(flag.equals(“success”)){
  115. //为session传递相应的值,用于在session过程中记录相关用户信息
  116. session.put(“s_userid”, userid);
  117. session.put(“s_username”, name);
  118. session.put(“s_sessionid”, sessionid);
  119. return true;
  120. }
  121. else{
  122. return false;
  123. }
  124. }
  125. else{
  126. return false;
  127. }
  128. }
  129. } catch (ClientProtocolException e) {
  130. // TODO Auto-generated catch block
  131. e.printStackTrace();
  132. } catch (IOException e) {
  133. // TODO Auto-generated catch block
  134. e.printStackTrace();
  135. }
  136. return false;
  137. }
  138. }

复制代码

 

2. LoginSuccessActivity.java主要获取php的session唯一的标识id以及用户的一些基本信息,session id则作为本次用户登录状态在服务器的唯一标识,即确定用户的唯一状态进行相关操作。LoginSuccessActivity.java类的方法与 GetWebSession.java类似。其主要功能是获取session id后再次发送session id到服务器进行验证,根据封装的session数据验证用户操作权限等。

 

代码如下:

 

  1. package com.login.main;
  2. import java.io.IOException;
  3. import java.io.UnsupportedEncodingException;
  4. import java.util.ArrayList;
  5. import java.util.HashMap;
  6. import java.util.List;
  7. import org.apache.http.HttpEntity;
  8. import org.apache.http.HttpResponse;
  9. import org.apache.http.client.ClientProtocolException;
  10. import org.apache.http.client.entity.UrlEncodedFormEntity;
  11. import org.apache.http.client.methods.HttpPost;
  12. import org.apache.http.impl.client.DefaultHttpClient;
  13. import org.apache.http.message.BasicNameValuePair;
  14. import org.apache.http.protocol.HTTP;
  15. import org.apache.http.util.EntityUtils;
  16. import org.json.JSONException;
  17. import org.json.JSONObject;
  18. import android.app.Activity;
  19. import android.content.Context;
  20. import android.content.Intent;
  21. import android.os.Bundle;
  22. import android.view.View;
  23. import android.view.View.OnClickListener;
  24. import android.widget.Button;
  25. import android.widget.TextView;
  26. import android.widget.Toast;
  27. public class LoginSuccessActivity extends Activity{
  28. private HashMap<String, String>session;
  29. @SuppressWarnings(“unchecked”)
  30. @Override
  31. protected void onCreate(Bundle savedInstanceState) {
  32. // TODO Auto-generated method stub
  33. super.onCreate(savedInstanceState);
  34. setContentView(R.layout.login_success);
  35. //获取从登录成功后界面的传递的参数
  36. session =  (HashMap<String, String>) this.getIntent()
  37. .getBundleExtra(“session”).getSerializable(“sessionid”);
  38. //读取session的基本信息,并显示相应的控件
  39. String userid_info=session.get(“s_userid”);
  40. String username_info=session.get(“s_username”);
  41. String session_id=session.get(“s_sessionid”);
  42. //显示相应的内容到控件
  43. TextView userid_show=(TextView)findViewById(R.id.userid_show);
  44. userid_show.setText(userid_info);
  45. TextView username_show=(TextView)findViewById(R.id.username_show);
  46. username_show.setText(username_info);
  47. TextView sessionid_show=(TextView)findViewById(R.id.sessionid_show);
  48. sessionid_show.setText(session_id);
  49. //根据本次session再次获取用户信息
  50. Button getInfo=(Button)findViewById(R.id.getinfo);
  51. getInfo.setOnClickListener(getInfoClick);
  52. }
  53. OnClickListener getInfoClick=new OnClickListener() {
  54. @Override
  55. public void onClick(View v) {
  56. // TODO Auto-generated method stub
  57. if(getUserInfo()){
  58. Context context = v.getContext();
  59. Intent intent = new Intent(context,
  60. GetUserInfoActivity.class);
  61. //传递session参数,在用户登录成功后为session初始化赋值,即传递HashMap的值
  62. Bundle map = new Bundle();
  63. map.putSerializable(“sessionid”, session);
  64. intent.putExtra(“session”, map);
  65. context.startActivity(intent); // 跳转到成功页面
  66. }else{
  67. Toast.makeText(v.getContext(), “数据为空!”, Toast.LENGTH_SHORT).show();
  68. }
  69. }
  70. };
  71. private boolean getUserInfo(){
  72. String sess_username=session.get(“s_username”);
  73. String sess_userid=session.get(“s_userid”);
  74. String sess_id=session.get(“s_sessionid”);
  75. DefaultHttpClient mHttpClient = new DefaultHttpClient();
  76. HttpPost mPost = new HttpPost(“http://10.0.2.2/web/php/getinfo.php”);
  77. List<BasicNameValuePair> pairs = new ArrayList<BasicNameValuePair>();
  78. pairs.add(new BasicNameValuePair(“sess_userid”, sess_userid));
  79. pairs.add(new BasicNameValuePair(“sess_username”, sess_username));
  80. pairs.add(new BasicNameValuePair(“sess_sessionid”, sess_id));
  81. try {
  82. mPost.setEntity(new UrlEncodedFormEntity(pairs, HTTP.UTF_8));
  83. } catch (UnsupportedEncodingException e) {
  84. // TODO Auto-generated catch block
  85. e.printStackTrace();
  86. }
  87. try {
  88. HttpResponse response = mHttpClient.execute(mPost);
  89. int res = response.getStatusLine().getStatusCode();
  90. if (res == 200) {
  91. HttpEntity entity = response.getEntity();
  92. if (entity != null) {
  93. String info = EntityUtils.toString(entity);
  94. System.out.println(“info———–“+info);
  95. //以下主要是对服务器端返回的数据进行解析
  96. JSONObject jsonObject=null;
  97. //flag为登录成功与否的标记,从服务器端返回的数据
  98. String flag=””;
  99. String userinfo=””;
  100. String level=””;
  101. String sessionid=””;
  102. try {
  103. jsonObject = new JSONObject(info);
  104. flag = jsonObject.getString(“flag”);
  105. userinfo = jsonObject.getString(“info”);
  106. level = jsonObject.getString(“level”);
  107. sessionid = jsonObject.getString(“sessionid”);
  108. } catch (JSONException e) {
  109. // TODO Auto-generated catch block
  110. e.printStackTrace();
  111. }
  112. //根据服务器端返回的标记,判断服务端端验证是否成功
  113. if(flag.equals(“notempty”)){
  114. //为session传递相应的值,用于在session过程中记录相关用户信息
  115. session.put(“info_userinfo”, userinfo);
  116. session.put(“info_level”, level);
  117. session.put(“info_sessionid”, sessionid);
  118. return true;
  119. }
  120. else{
  121. return false;
  122. }
  123. }
  124. else{
  125. return false;
  126. }
  127. }
  128. } catch (ClientProtocolException e) {
  129. // TODO Auto-generated catch block
  130. e.printStackTrace();
  131. } catch (IOException e) {
  132. // TODO Auto-generated catch block
  133. e.printStackTrace();
  134. }
  135. return false;
  136. }
  137. }

复制代码

 

3.GetUserInfoActivity.java类是根据用户登录后产生唯一session 标识进行操作获取用户详细信息的类。

 

代码如下:

 

  1. package com.login.main;
  2. import java.util.HashMap;
  3. import android.app.Activity;
  4. import android.os.Bundle;
  5. import android.widget.TextView;
  6. public class GetUserInfoActivity extends Activity{
  7. private HashMap<String, String>session;
  8. @SuppressWarnings(“unchecked”)
  9. @Override
  10. protected void onCreate(Bundle savedInstanceState) {
  11. // TODO Auto-generated method stub
  12. super.onCreate(savedInstanceState);
  13. setContentView(R.layout.get_info);
  14. //获取从登录成功后界面的再次传递的参数
  15. session =  (HashMap<String, String>) this.getIntent().
  16. getBundleExtra(“session”).getSerializable(“sessionid”);
  17. //读取session的基本信息,并显示相应的控件
  18. String session_info=session.get(“info_userinfo”);
  19. String session_level=session.get(“info_level”);
  20. String session_id=session.get(“info_sessionid”);
  21. //显示相应的内容到控件
  22. System.out.println(“session_info——–“+session_info);
  23. TextView get_info=(TextView)findViewById(R.id.get_info);
  24. get_info.setText(session_info);
  25. TextView get_level=(TextView)findViewById(R.id.get_level);
  26. get_level.setText(session_level);
  27. TextView get_sessionid=(TextView)findViewById(R.id.get_sessionid);
  28. get_sessionid.setText(session_id);
  29. }
  30. }

复制代码

 

4.三个布局的xml文件

 

(1.)main.xml

 

  1. <?xml version=”1.0″ encoding=”utf-8″?>
  2. <LinearLayout xmlns:android=”http://schemas.android.com/apk/res/android”
  3. android:orientation=”vertical”
  4. android:layout_width=”fill_parent”
  5. android:layout_height=”fill_parent” >
  6. <TextView android:layout_height=”wrap_content”
  7. android:layout_width=”wrap_content”
  8. android:text=”用户”></TextView>
  9. <EditText android:layout_height=”wrap_content”
  10. android:text=”” android:layout_width=”fill_parent”
  11. android:singleLine=”true” android:id=”@+id/user”  ></EditText>
  12. <TextView android:layout_height=”wrap_content”
  13. android:layout_width=”wrap_content”
  14. android:text=”密码”></TextView>
  15. <EditText android:id=”@+id/password”
  16. android:layout_height=”wrap_content”
  17. android:text=”” android:layout_width=”fill_parent”
  18. android:password=”true” android:singleLine=”true”></EditText>
  19. <LinearLayout android:layout_height=”wrap_content”
  20. android:layout_width=”fill_parent”
  21. android:orientation=”horizontal”
  22. android:paddingLeft=”0dip”>
  23. <TableRow android:layout_width=”fill_parent”
  24. android:layout_height=”wrap_content”>
  25. <Button android:layout_height=”fill_parent”
  26. android:layout_width=”fill_parent” android:text=”登录”
  27. android:id=”@+id/loginBtn”
  28. android:layout_weight=”1″></Button>
  29. <Button android:layout_height=”fill_parent”
  30. android:layout_width=”fill_parent”
  31. android:text=”退出”
  32. android:id=”@+id/logoutBtn”
  33. android:layout_weight=”1″></Button>
  34. </TableRow> </LinearLayout> </LinearLayout>

复制代码

(2.)login_success.xml

  1. <?xml version=”1.0″ encoding=”utf-8″?>
  2. <LinearLayout xmlns:android=”http://schemas.android.com/apk/res/android”
  3. android:layout_width=”fill_parent” android:layout_height=”fill_parent”
  4. android:orientation=”vertical”>
  5. <LinearLayout android:layout_height=”wrap_content”
  6. android:layout_width=”fill_parent”
  7. android:orientation=”horizontal”
  8. android:paddingLeft=”0dip”>
  9. <TextView
  10. android:layout_height=”fill_parent”
  11. android:layout_width=”wrap_content”
  12. android:text=”用户ID:”   >
  13. </TextView>
  14. <TextView android:layout_height=”fill_parent”
  15. android:layout_width=”fill_parent”
  16. android:text=””
  17. android:id=”@+id/userid_show” ></TextView>
  18. </LinearLayout>
  19. <LinearLayout android:layout_height=”wrap_content”
  20. android:layout_width=”fill_parent”
  21. android:orientation=”horizontal”
  22. android:paddingLeft=”0dip”>
  23. <TextView android:layout_height=”fill_parent”
  24. android:layout_width=”wrap_content”
  25. android:text=”用户名: ”   ></TextView>
  26. <TextView android:layout_height=”fill_parent”
  27. android:layout_width=”fill_parent”
  28. android:text=””
  29. android:id=”@+id/username_show” ></TextView>
  30. </LinearLayout>
  31. <LinearLayout android:layout_height=”wrap_content”
  32. android:layout_width=”fill_parent”
  33. android:orientation=”horizontal”
  34. android:paddingLeft=”0dip”>
  35. <TextView android:layout_height=”fill_parent”
  36. android:layout_width=”wrap_content”
  37. android:text=”本次会话:”   ></TextView>
  38. <TextView android:layout_height=”fill_parent”
  39. android:layout_width=”fill_parent”
  40. android:text=””
  41. android:id=”@+id/sessionid_show” ></TextView>
  42. </LinearLayout>
  43. <LinearLayout android:layout_height=”wrap_content”
  44. android:layout_width=”fill_parent”
  45. android:orientation=”horizontal”
  46. android:paddingLeft=”0dip”>
  47. <Button android:layout_height=”fill_parent”
  48. android:layout_width=”wrap_content”
  49. android:id=”@+id/getinfo”
  50. android:text=”根据本次会话再次获取用户信息”
  51. ></Button>
  52. </LinearLayout>
  53. </LinearLayout>

复制代码

(3.)get_info.xml

  1. <?xml version=”1.0″ encoding=”utf-8″?>
  2. <LinearLayout xmlns:android=”http://schemas.android.com/apk/res/android”
  3. android:layout_width=”fill_parent”
  4. android:layout_height=”fill_parent”
  5. android:orientation=”vertical”>
  6. <LinearLayout android:layout_height=”wrap_content”
  7. android:layout_width=”fill_parent”
  8. android:orientation=”horizontal”
  9. android:paddingLeft=”0dip”>
  10. <TextView android:layout_height=”fill_parent”
  11. android:layout_width=”wrap_content”
  12. android:text=”用户信息: ”   >
  13. </TextView>
  14. <TextView android:layout_height=”fill_parent”
  15. android:layout_width=”fill_parent”
  16. android:text=””
  17. android:id=”@+id/get_info” ></TextView>
  18. </LinearLayout>
  19. <LinearLayout android:layout_height=”wrap_content”
  20. android:layout_width=”fill_parent”
  21. android:orientation=”horizontal”
  22. android:paddingLeft=”0dip”>
  23. <TextView android:layout_height=”fill_parent”
  24. android:layout_width=”wrap_content”
  25. android:text=”用户级别:”   ></TextView>
  26. <TextView android:layout_height=”fill_parent”
  27. android:layout_width=”fill_parent”
  28. android:text=””
  29. android:id=”@+id/get_level” ></TextView>
  30. </LinearLayout>
  31. <LinearLayout android:layout_height=”wrap_content”
  32. android:layout_width=”fill_parent”
  33. android:orientation=”horizontal”
  34. android:paddingLeft=”0dip”>
  35. <TextView android:layout_height=”fill_parent”
  36. android:layout_width=”wrap_content”
  37. android:text=”本次会话:”   ></TextView>
  38. <TextView android:layout_height=”fill_parent”
  39. android:layout_width=”fill_parent” android:text=””
  40. android:id=”@+id/get_sessionid” ></TextView>
  41. </LinearLayout>
  42. <LinearLayout android:layout_height=”wrap_content”
  43. android:layout_width=”fill_parent”
  44. android:orientation=”horizontal”
  45. android:paddingLeft=”0dip”> </LinearLayout> </LinearLayout>

复制代码

2.)服务器端(php)

php服务器端主要有三个文件,conn.php,login.php和getinfo.php。

 

1. conn.php是连接数据库的配置文件。

 

2. login.php主要是用来验证android客户端发送的请求,请求成功则返回flag=’success’的状态标识,采用数组记录用户基本信息, 存储用户数据到session,并且记录本次产生的session id。用户基本数据及本次session产生的id均封装成json格式(json_encode($arr)),发送android客户端。产生本次 session id的方法

 

$sessionid=session_id();//注意没有参数

 

具体代码如下:

 

  1. <?php
  2. header(“Content-Type: text/html; charset=utf-8”) ;
  3. //包含数据库连接文件
  4. include(‘conn.php’);
  5. session_start();
  6. $username = htmlspecialchars($_POST[“username”]);
  7. $password=$_POST[“password”];
  8. mySQL_query(“set names utf8”);
  9. //检测用户名及密码是否正确
  10. $check_query = mySQL_query(“select id ,name from user where name=’$username’ and
  11. password=’$password’ limit 1″);
  12. $arr=array();//空的数组,该数组主要是格式化数据并封装成json格式发送到客户端
  13. if($result = mysql_fetch_array($check_query)){
  14. //登录成功
  15. $_SESSION[‘username’] = $result[‘name’];
  16. $_SESSION[‘userid’] = $result[‘id’];
  17. //获取当前session id
  18. $sessionid=session_id();
  19. $_SESSION[‘$sessionid’] = $sessionid;
  20. $arr = array(
  21. ‘flag’=>’success’,
  22. ‘name’=>$result[‘name’],
  23. ‘userid’=>$result[‘id’],
  24. ‘sessionid’=>$sessionid
  25. );
  26. //封装json,如果php版本低于5.2,则不支持json_encode()方法,
  27. //可以参考本文件夹中php_json_encode.php中php_json_encode()方法代替json_encode();
  28. echo json_encode($arr);
  29. } else {
  30. $arr = array(
  31. ‘flag’=>’error’,
  32. ‘name’=>”,
  33. ‘userid’=>”,
  34. ‘sessionid’=>”
  35. ); //封装json,如果php版本低于5.2,则不支持json_encode()方法,
  36. //可以参考本文件夹中php_json_encode.php中php_json_encode()方法代替json_encode();
  37. echo json_encode($arr);
  38. }
  39. ?>

复制代码

 

3. getinfo.php文件主要是用户再次查询信息验证session,而不是重新产生session,以记录用户状态。通过验证flag是否为empty判断数据是否显示。最后封装成json发送到客户端

 

获取本次session的方法:

 

$sessionid=$_POST[“sess_sessionid”];//获取android客户端的sessionid

 

session_id($sessionid);//有参数

 

session_start();//启动session

 

具体代码如下:

 

  1. <?php
  2. header(“Content-Type: text/html; charset=utf-8”) ;
  3. include(‘conn.php’);
  4. //获取从客户端LoginSuccessActivity类传递的参数
  5. $userid=$_POST[“sess_userid”];
  6. $username=$_POST[“sess_username”];
  7. //获取客户端传递的session标识
  8. $sessionid=$_POST[“sess_sessionid”];
  9. session_id($sessionid);
  10. //将会根据session id获得原来的session
  11. session_start();
  12. //获取服务器端原来session记录的username,并且根据客户端传过来的username比较进行验证操作
  13. $sess_username=$_SESSION[‘username’];
  14. if($username==$sess_username){
  15. mysql_query(“set names utf8”);
  16. //查询用户基本信息
  17. $check_query = mysql_query(“select userinfo,level from info where userid=’$userid’  limit 1”);
  18. $arr=array();//空的数组
  19. if($result = mysql_fetch_array($check_query)){
  20. $arr = array(
  21. ‘flag’=>’notempty’,
  22. ‘info’=>$result[‘userinfo’],
  23. ‘level’=>$result[‘level’],
  24. ‘sessionid’=>$sessionid
  25. );
  26. echo json_encode($arr);
  27. }
  28. } else {
  29. $arr = array(
  30. ‘flag’=>’empty’,
  31. ‘name’=>”,
  32. ‘userid’=>”,
  33. ‘sessionid’=>$sessionid
  34. );
  35. echo json_encode($arr);
  36. }
  37. ?>

复制代码

3.)数据库端(mysql)

采用mysql建立数据库,建立两个简单的数据表:user和info。

 

  1. /*
  2. MySQL Data Transfer
  3. Source Host: localhost
  4. Source Database: login
  5. Target Host: localhost
  6. Target Database: login
  7. Date: 2011-6-14 11:10:46
  8. */
  9. SET FOREIGN_KEY_CHECKS=0;
  10. — —————————-
  11. — Table structure for info
  12. — —————————-
  13. CREATE TABLE `info` (
  14. `id` int(12) NOT NULL AUTO_INCREMENT,
  15. `userid` int(12) DEFAULT NULL,
  16. `userinfo` varchar(100) DEFAULT NULL,
  17. `level` int(2) DEFAULT NULL,
  18. PRIMARY KEY (`id`),
  19. KEY `useid` (`userid`),
  20. CONSTRAINT `useid` FOREIGN KEY (`userid`) REFERENCES `user` (`id`)
  21. ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
  22. — —————————-
  23. — Table structure for user
  24. — —————————-
  25. CREATE TABLE `user` (
  26. `id` int(12) NOT NULL AUTO_INCREMENT,
  27. `name` varchar(20) DEFAULT NULL,
  28. `password` varchar(20) DEFAULT NULL,
  29. `status` int(2) DEFAULT NULL,
  30. PRIMARY KEY (`id`)
  31. ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
  32. — —————————-
  33. — Records
  34. — —————————-
  35. INSERT INTO `info` VALUES (‘1’, ‘1’, ‘charlie  is a developer.’, ‘1’);
  36. INSERT INTO `info` VALUES (‘2’, ‘2’, ‘william is a boss.’, ’20’);
  37. INSERT INTO `user` VALUES (‘1’, ‘charlie’, ‘password’, ‘1’);
  38. INSERT INTO `user` VALUES (‘2’, ‘william’, ‘mypassword’, ‘2’);

复制代码

运行效果如图:
user-login-ui.jpg

2012-5-8 14:14 上传

下载附件 (149.96 KB)

图 -1 GetWebSession.java类的布局
login-and-get-session.jpg

2012-5-8 14:12 上传

下载附件 (61.43 KB)

图 -2 LoginSuccessActivity.java类获取的session id以及用户基本信息
get-user-info-by-session-again.jpg

[转载]通过Android View的两种事件响应方法比较inheritance和composition - 山水含清晖 - 博客园

mikel阅读(1069)

[转载]通过Android View的两种事件响应方法比较inheritance和composition – 山水含清晖 – 博客园.

Android view有两种主要的处理事件的方式,

  1. 在View的子类中覆盖onXXX方法。因为这是在子类中通过覆盖的方式来响应事件,我称之为基于继承(inheritance)的响应方式。
  2. 调用View.setXXXListener,参数会实现View.OnXXXListener接口。因为View对象和Listner组合起来完成工作,我称之为基于组合(composition)的响应方式。

例如对于touch事件而言,View.dispatchTouchEvent接收到touch事件对象,然后:

  1. 调用通过View.setOnTouchListener注册的listener。
  2. 调用可以被子类覆盖的onTouchEvent方法。

举个简单例子,一个Activity中有一个edit框(EditText对象)。EditText是通过代码添加的到Activity中的。我希望在点击它时,自动把该框的底色设置为红色。

如果采用第一种方式,其EditText的子类是:

public class TouchChangeBackgroundColorEditText extends EditText {

    public MyEditText(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        this.setBackgroundColor(Color.RED);
        return super.onTouchEvent(event);
    }
}

然后我们在Activity中使用该EditText的子类。

如果采用第二种方法,则需要创建一个对touch事件的listener:

public class TouchChangeBackgroundColorListener implements View.OnTouchListener {

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        v.setBackgroundColor(Color.RED);
        return false;
    }
}

然后在Activity中使用普通EditText,并对该EditText调用setOnTouchListener(new TouchChangeBackgroundColorListener)。

然后用户又想对某些EditText使用Key event来改变背景颜色,那如果采用基于继承的方式,用户需要创建KeyChangeBackGroundColorEditText的子类,在其中覆盖onKeyDown方法;如果采用组合的方式,则需要实现OnKeyListener并添加到EditText中。

现在问题来了,用户希望某些EditText是不响应touch和key事件的,有些只响应一种,有些两种都响应。那么如果采用继承的方式,那需要四种对象,一个基本都EditText和三种针对only touch,only key和both touch and key的子类。

但如果采用组合的方式,我们仍然只需要两个listener,只需对不同的EditText添加不同的listener即可。

如果更复杂,我们还想针对trackball改变颜色,那如果采用继承,则可能有3×3=9种类。但采用组合,只需要三种listener,然后EditText根据需要添加即可。

所以说,在可能有多种因素导致变化的时候,继承可能导致对象种类(即类)爆炸式增长;而组合可以用不同的对象封装不同的变化,减少类的个数。

但这里减少的只是编码中类的个数,而在运行时如果采用继承,那对象的个数是EditText的个数;但如果采用组合,那每个EditText都对应着一个Listener,所以有更多的对象个数。所以说,组合一般较继承产生更多的运行时对象,这需要更多的内存(对象存储)和运算时间(对象间相互调用)。

现在假设app的界面上已经有一个EditText控件,我们又想在app中通过选项来控制该EditText的行为,例如我们有三个选项,分别对应touch,key和trackball事件发生时是否改变该EditText的背景颜色。那么如果采用组合,可以先创建好三个listener,然后根据选择添加listener到现有的的EditText中。但如果使用继承,根本是无法动态改变其行为了。所以说,组合可以动态改变对象的行为,而继承只能在静态改变。

再回到前面继承的onTouchEvent方法,在最后一行,我需要调用super.onTouchEvent。事实上,我开始实现时忘记调用这行了,结果点击edit框时,可以改变颜色,但无法切出输入法。在现实中覆盖父类的方法时,有的父类方法是需要在子类中被调用的,但有的却不需要。覆盖的时候要详细查看父类文档和代码。但如果采用组合的方式,我只需要关系listener自己的视线即可,并不需要调用view中跟touch处理相关的代码。所以说,继承是一种白盒复用,在覆盖父类的方法时,需要关心父类方法的实现;但组合是黑盒测试,我只需要实现接口即可,而不需要关心该接口如何被调用。

故而Design pattern中所有模式的两大基石之一就是:Favor object composition over class inheritance。

[转载]iOS- 显示数据列表最常用的一个控件UITableView - 清澈Saup - 博客园

mikel阅读(1161)

[转载]iOS- 显示数据列表最常用的一个控件UITableView – 清澈Saup – 博客园.

相信做过iOS的程序员,最熟悉的控件一定少不了UITableView,最常用的控件也一定少不了UITableView!

今天分享一下自己对UITableView的实现大体思路,和整理出来的学习笔记!

1.UITableView里的结构图                       

 

2.UITableView数据展示的条件                      

1> UITableView的所有数据都是由数据源(dataSource)提供的,所以要想在UITableView展示数据,必须设置UITableViewdataSource数据源对象

2> 要想当UITableViewdataSource对象,必须遵守UITableViewDataSource协议,实现相应的数据源方法

3> UITableView想要展示数据的时候,就会给数据源发送消息(调用数据源方法),UITableView会根据方法返回值决定展示怎样的数据

 

3.数据展示的过程                             

数据显示,三步走

1> 先调用数据源的

– (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView

得知一共有多少组

 

2> 然后调用数据源的

– (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section

得知第section组一共有多少行

 

3> 然后调用数据源的

– (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath

得知indexPath.section组 第indexPath.row 行显示怎样的cell(显示什么内容)

 

4.开发中经常用到的UITableView数据源方法               

1> 一共有多少组

– (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView

 

@required  2.3 是必须实现

2> 第section组一共有多少行

– (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section

 

3> 第indexPath.section组 第indexPath.row行显示怎样的cell(显示什么内容)

– (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath

 

4> 第section组显示怎样的头部标题

– (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section;

 

5> 第section组显示怎样的尾部标题

– (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section;

 

5.开发中经常用到的UITableView代理方法               

1.- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath

选中了UITableView的某一行

2.- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath

某一行的高度

3.- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section

第section分区头部的高度

4.- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section

第section分区尾部的高度

5.- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section

第section分区头部显示的视图

6.- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section

第section分区尾部显示的视图

7.- (NSInteger)tableView:(UITableView *)tableView indentationLevelForRowAtIndexPath:(NSIndexPath *)indexPath

设置每一行的等级缩进(数字越小,等级越高)

 

6.tableView刷新数据的方式                      

 1> 修改模型数据

2> 刷新表格

– reloadData

整体刷新(每一行都会刷新)

– (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation

局部刷新

[转载]iOS- iOS 和 Android 的后台推送原理各是什么?有什么区别? - 清澈Saup - 博客园

mikel阅读(1216)

[转载]iOS- iOS 和 Android 的后台推送原理各是什么?有什么区别? – 清澈Saup – 博客园.

iOS 的推送
iOS 在系统级别有一个推送服务程序使用 5223 端口。使用这个端口的协议源于 Jabber 后来发展为 XMPP ,被用于 Gtalk 等 IM 软件中。

所以, iOS 的推送,可以不严谨的理解为:
苹果服务器朝手机后台挂的一个 IM 服务程序发送的消息。
然后,系统根据该 IM 消息识别告诉哪个 Apps 具体发生了什么事。
然后,系统分别通知这些 Apps 。

这个消息的内容是这样的:
应该说,苹果这种方式在技术上没有什么创新。但是,整个架构是很了不起的。 因为:

使用久经考验的协议,技术风险小。


苹果勇于承担责任:
他需要维护一个代价不小的服务器集群,而且要为服务器的 down 机负责。

选择低风险的技术方案 Bug 更少,减轻了用户的痛苦,这是构架师的功劳。
苹果承担责任,尽可能的减少了不可控的意外,保证了用户体验。
这,只能说是公司决策者的功劳。
(从侧面说明有个懂技术的 VP 是多重要。。。而 Scott 走人了。。)

他们带给用户的好处也是实实在在的。
1 安全。
只有登录过的开发者可以通过苹果的服务器推送。

2 快速,稳定,可靠。
苹果掌控推送服务器和 OS 。

3 更省电。

4 让整个系统的体验更统一和简单。
不会出现杀后台这种脑残事。(不用大量 Apps / Apps 的服务为了推送挂后台)。
也不会出现 Apps 被杀就收不到推送这种脑残事(早一点的新浪微博 Android 版仍然如此)。

5 开发容易。
当然,开发者还是要做些事情,比如维护个服务器什么的: ifanr.com/3979。但是复杂度无疑降低很多了。

Android 的推送
Apps 挂后台一直是 Android 引以为豪的特性(虽然我真的不知道是好处多还是坏处多。。)。。。大家挂后台等待推送就成为技术选择。

当然, Google 事后也提供类似苹果的推送方式了。倒也谈不上抄袭,毕竟苹果的整个技术实现也没有什么特别创新之处。

用户的电池? 

Apps 的开发者不会站在系统层面考虑的。他会假设其他 Apps 没有那么“不自觉”。而 Google 不强制的结果就是:没人真正为用户的电池负责。

但是, Google 的方案也并非全是悲剧:
也因为整个技术方案非强制, Android 的 Apps 在接收到推送后的表现更为灵活。
像 Line 的 Android 版本可以在推送通知的 Popup 上直接回复, iOS 就需要越狱才能做到了。

最后的话
强制和封闭,有时候并非坏事。他意味着做出这个决定的人,要为此负责。

所以,如果说苹果的推送方案有何创新?

我以为是超越技术,不惜让公司承担更多风险和责任的解决方案。(类似的还有 BB 的专用网络, Kindle 的全球 3G )

个人相信,担负起这些“额外”的责任,是值得的。。。

只要是为了用户。

[转载]如何打一手好Log - luguo3000 - 博客园

mikel阅读(1444)

[转载]如何打一手好Log – luguo3000 – 博客园.

  如果项目上过线的话,那你一定知道Log是多么重要。

为什么说Log重要呢?因为上线项目不允许你调试,你只能通过Log来分析问题。这时打一手好Log的重要性绝不亚于写一手好代码。项目出问题 时,你要能拿出Log证明自己负责的部分没有问题,如果是自己的问题,要从Log里快速找出错误原因。如果没有从Log里找出错误原因,那一定是一件很悲 催的事情,特别是在bug不容易重现的情况下。那简直就是叫天天不灵,叫地地不应啊。

 

一.Log级别

Log最常用的级别就是Debug,INFO,WARN,ERROR,其他的很少用。如何运用合适的Log级别也是非常重要的,在不该用ERROR的地方用了ERROR,可能会给你带来额外的麻烦。下边仅根据自己的使用习惯,分别说一下我对各种级别的理解。

1.ERROR:

ERROR是错误的意思,但不代表出现异常的地方就该打ERROR。我认为ERROR是相对程序正确运行来说的,如果出现了ERROR那就代表 出问题了,开发人员必须要查一下原因,或许是程序问题,或许是环境问题,或许是理论上不该出错的地方出错了。总之,如果你觉得某个地方出问题时需要解决, 就打ERROR,如果不需要解决就不要打ERROR。

举例来说,如果有一个接口。调用者传过来的参数不在你的接受范围内,在这种情况下你不能打ERROR,因为传什么值是用户决定的,并不影响程序 正确运行。想象一下,如果你的服务器上有监控程序的话,检测到ERROR或WARN就报警,参数错误你也打ERROR,那运维人员会疯掉的。

如果做一个对讲机,在解析语音数据包时出错了,那就要打ERROR了,因为这个是理论上不该出错的地方,要不就是你的解析代码有问题,要不就是开发人员在拼凑语音包时存在问题,这个时候需要你来找出问题的原因。所以应该打ERROR。

2.WARN:

WARN是指出现了不影响程序正确运行的问题,WARN也是问题但不影响程序正常运行,如果WARN出现的过于频繁或次数太多,那就代表你要检查一下程序或环境或依赖程序是否真的出问题了。

假如你访问一个接口,设置了一个超时,超时之后会抛异常,你在try块里不该打ERROR也不该打INFO来无视它,这时你应该打WARN,紧紧是警告一下,如果超时过多那就该检查一下了,是不是对方接口有问题了或者是网络环境出问题了。

3.INFO和Debug

ERROR和WARN是指有问题,而INFO和DEBUG就是指一般的信息了。在程序出问题时,如果这条log可以帮助你分析问题或查看程序的 运行情况,那就应该打个INFO。如果仅仅是为了在调试阶段查看程序是否运行正确那就要打DEBUG。前边讨论的接口参数错误问题,就应该打个INFO 了,调用者说你的接口总是返回错误代码,你可以告诉他,是他的哪个参数传错了。

 

二.如何打

1.log必备信息

在每一条log中都要将时间、类名及函数名,可以的话将行号也打印出来(不建议手写行号),像java的log4j就是不错的。

2.函数开始结束处

在重要函数的开始结束出应该打上log ,这样在看log时会比较直观,什么时候开始什么时候结束就会一目了然,万一中间出异常导致程序退出了,也知道是在哪个函数突然中断的。也同样适用于一个重要逻辑块的开始结束。

3.返回结果

尽量在重要函数或web接口的每个返回分支打印返回结果。在出现不好分析的异常时,从细节下手,这时log会派上用场。如果跟合作方在数据方面出现争议也可以及时拿出证据。

4.添加Exception异常的捕获

如果你在代码中捕获了某种异常,那你要在try块后添加Exception的捕获,以防出现运行时异常中断程序。

5.务必打印堆栈信息

在异常捕获代码中务必要将堆栈信息打印出来,否则打了那么多的log可能会功亏一篑。

6.多线程的log

在多线程的程序中,log最好要标记thredId,否则可能不知道是哪个线程的作业,也无法有条理的来观察一个线程。

7.成功失败标志

如果某个函数是做一件比较关键的事情,那么这件事情成功还是失败了,要打印log,否则关键事件运行结果如何都拿不出证据的话,实在是不能让人信服。

8.前后log的关系

如果是web程序或接口,那log就不是按照你预定的顺序出现的,可能是好几个响应的log穿插在一起的。代码里如果有几条log前后存在一定 的数据关系,那么要将这几条log的关联信息打出来,用来确定是针对同一个响应的。如果没有明确的标志,很难说后边的log跟前边的log是同一个响应或 者是针对同一条数据。

9.关于耗时

访问一个第三方接口、上传下载文件等可能耗时的操作,都要记录完成这个操作所耗的时间。否则程序性能出了问题,你不知道是网络原因呢,还是你调用的第三方接口性能出现问题呢,还是你自己程序的问题呢。

10.关于数量

涉及到数量的操作要打印log,比如查询数据库和批量拷贝文件、上传下载、批量格式转换等批量操作,设计到的数量要打印出来。

 

总之,打log的目的是为了迅速排错或在有争议时拿出证据证明自己。基于这个目的,log不在多,只要抓住一切对自己有利的信息,就可以了。

想起其他的再继续补充吧,欢迎大家拍砖补充。

本人学识尚浅,写文目的是为了得到大家指点。 倘若文章帮到了您,那真是好极了。