2019年1月13日 By mikel 分类: 数据库
http://baa.im/847971

来源: SQL Server2005 异常处理机制(Begin try Begin Catch) – dear_Alice_moon的专栏 – CSDN博客

begin try
SQL
end trybegin catch –SQL (处理出错动作)
end catch

我们将可能会出错的sql 写在begin try…end try 之间,若出错,刚程序就跳到紧接着的begin try…end try 的beign catch…end catch

中,执行beign catch…end catch错误处理SQL。try..catch 是可以嵌套的。

在begin catch …end catch中我们可以利用系统提供的下面四个函数得到出错信息:

error_number 返回错误代码

error_serverity 返回错误的严重级别

error_state 返回错误状态代码

error_message 返回完整的错误信息

上面的四个函数在同一个begin catch …end catch可以在多次使用,值是不变的。

下面是一个简单的小例子。

begin try
select 2/0
end try

begin catch
select error_number() as error_number ,
error_message() as error_message,
error_state() as error_state,
error_severity() as error_severity
end catch

结果:

—–
error_number error_message error_state error_severity

8134 遇到以零作除数错误。 1 16

——————————————————-

 不受 TRY…CATCH 构造影响的错误
TRY…CATCH 构造在下列情况下不捕获错误:

严重级别为 10 或更低的警告或信息性消息。

严重级别为 20 或更高且终止会话的 SQL Server 数据库引擎任务处理的错误。 如果所发生错误的严重级别为 20 或更高,而数据库连接未中断,则 TRY…CATCH 将处理该错误。

需要关注的消息,如客户端中断请求或客户端连接中断。

当系统管理员使用 KILL 语句终止会话时。

 

USE AdventureWorks;
GO

BEGIN TRY
— Generate a divide-by-zero error.
SELECT 1/0;
END TRY
BEGIN CATCH
SELECT
ERROR_NUMBER() AS ErrorNumber,
ERROR_SEVERITY() AS ErrorSeverity,
ERROR_STATE() AS ErrorState,
ERROR_PROCEDURE() AS ErrorProcedure,
ERROR_LINE() AS ErrorLine,
ERROR_MESSAGE() AS ErrorMessage;
END CATCH;
GO

 

USE AdventureWorks;
GO
BEGIN TRANSACTION;

BEGIN TRY
— Generate a constraint violation error.
DELETE FROM Production.Product
WHERE ProductID = 980;
END TRY
BEGIN CATCH
SELECT
ERROR_NUMBER() AS ErrorNumber,
ERROR_SEVERITY() AS ErrorSeverity,
ERROR_STATE() as ErrorState,
ERROR_PROCEDURE() as ErrorProcedure,
ERROR_LINE() as ErrorLine,
ERROR_MESSAGE() as ErrorMessage;

IF @@TRANCOUNT > 0
ROLLBACK TRANSACTION;
END CATCH;

IF @@TRANCOUNT > 0
COMMIT TRANSACTION;
GO

SQL Server2005 异常处理机制(Begin try Begin Catch) – dear_Alice_moon的专栏 – CSDN博客已关闭评论
2019年1月13日 By mikel 分类: 数据库
http://baa.im/847971

来源: EXECUTE 后的事务计数指示 BEGIN 和 COMMIT 语句的数目不匹配。上一计数 = 0,当前计数 = 1。 – yjc2629498 – 博客园

出现这种错误有两种情况

情况一:

设置了事务开始而没有对事务进行提交或回滚 例如:

Begin  Tran

Declare @i int

if(@i>10)

Begin

Return ‘Error’

End

Commit Tran

Return ‘Ok’

当@i>10时 则会报 EXECUTE 后的事务计数指示 BEGIN 和 COMMIT 语句的数目不匹配。上一计数 = 0,当前计数 = 1。

情况二:

发生在嵌套事务里 如果子存储过程进行了子事务的回滚

 

  1. declare @trancount int –commit,rollback只控制本存储过程 
  2.     set @trancount = @@trancount;
  3.     if (@trancount=0) /*判断事务记数,根据情况确定使用保存点或者新建一个事务*/
  4.         begin tran current_tran–当前事务点,rollback、commit都从这里开始  
  5.     else
  6.         save tran current_tran
declare @trancount int --commit,rollback只控制本存储过程
    set @trancount = @@trancount;
    
	if (@trancount=0) /*判断事务记数,根据情况确定使用保存点或者新建一个事务*/ 
		begin tran current_tran--当前事务点,rollback、commit都从这里开始 
	else
		save tran current_tran

…….

….做事去了

…….

 

  1. if @error_code != 0 or @logErrorCode != 1
  2.         begin
  3.             rollback tran current_tran
  4.             set @error_code = -1; — 失败 
  5.         end
  6.     else
  7.         begin
  8.             commit tran current_tran
  9.             set @error_code = 1; — 成功 
  10.          end
if @error_code != 0 or @logErrorCode != 1
		begin
			rollback tran current_tran
			set @error_code = -1; -- 失败
		end
	else
		begin
			commit tran current_tran
			set @error_code = 1; -- 成功
		 end

有没有问题?(current_tran是保存点哈,不明白的,后面有比较详细的介绍)

我用了好久了(在一个项目里面),可是突然有一天,也就是今天,它出事了。原因嘛,虽然写的是嵌套的,之前都没有嵌套调到过。

我在外围开了一个事务,再来调这个存储过程,当它 commit tran current_tran 时(rollback tran current_tran是不会有事的),会出什么错误?如果你不能很明确的告诉我,说明你还没有理解得深刻。做个选择吧?

1.”…BEGIN 和 COMMIT 语句的数目不匹配。上一计数 = 0,当前计数 = 1。”

2.”…BEGIN 和 COMMIT 语句的数目不匹配。上一计数 = 1,当前计数 = 0。

 

答案:【2】。

线索分析:我是在外部开了一个事务的,所以在未进入该存储过程以前@@trancount的值应该为1;进入时,save tran current_tran, @@trancount值没有变;完事的,执行commit tran current_tran,@@trancount的值应该为0;--所以,进入前,出来后,@@trancount值发生了改变,SQLServer不干了(原因,自己去想吧:拆散了begin tran 配对)。

怎么解决

1.进入子事务前先记录@@trancount,我们用变量@trancount来记录。

2. 提交子事务前,先判断之前的@trancount是否为0;为0表示”该事务”前没有事务调用,可以直接提交事务;不为0,表明进入该事务前已经有一个事务,该事务是子事务,不能提交。

  1. — 如果当前计数为0,则提交.  
  2.          — 因为Commit tran ,@@TRANCOUNT会减1。嵌套事务时,调用该存在过程(作为子过程,此时@@TRANCOUNT > 0), 
  3.          — 只是保存了tran, @@TRANCOUNT没有发生改变;直接Commit会使@@TRANCOUNT减1,会打破事务对(Begin Tran) 
  4.         if(@trancount = 0)
  5.         begin
  6.             commit tran current_tran
  7.         end
  8.         set @error_code = 1; — 成功
 -- 如果当前计数为0,则提交. 
			 -- 因为Commit tran ,@@TRANCOUNT会减1。嵌套事务时,调用该存在过程(作为子过程,此时@@TRANCOUNT > 0),
			 -- 只是保存了tran, @@TRANCOUNT没有发生改变;直接Commit会使@@TRANCOUNT减1,会打破事务对(Begin Tran)
			if(@trancount = 0)
			begin
			    commit tran current_tran
			end
			set @error_code = 1; -- 成功
EXECUTE 后的事务计数指示 BEGIN 和 COMMIT 语句的数目不匹配。上一计数 = 0,当前计数 = 1。 – yjc2629498 – 博客园已关闭评论
2019年1月13日 By mikel 分类: 数据库
http://baa.im/847971

来源: SQL Server中的事务与锁 – 张龙豪 – 博客园

了解事务和锁

事务:保持逻辑数据一致性与可恢复性,必不可少的利器。

锁:多用户访问同一数据库资源时,对访问的先后次序权限管理的一种机制,没有他事务或许将会一塌糊涂,不能保证数据的安全正确读写。

死锁:是数据库性能的重量级杀手之一,而死锁却是不同事务之间抢占数据资源造成的。

不懂的听上去,挺神奇的,懂的感觉我在扯淡,下面带你好好领略下他们的风采,嗅査下他们的狂骚。。

先说事务–概念,分类

用华仔无间道中的一句来给你诠释下:去不了终点,回到原点。

举例说明:

在一个事务中,你写啦2条SQL语句,一条是修改订单表状态,一条是修改库存表库存-1 。 如果在修改订单表状态的时候出错,事务能够回滚,数据将恢复到没修改之前的数据状态,下面的修改库存也就不执行,这样确保你关系逻辑的一致,安全。。

事务就是这个样子,倔脾气,要么全部执行,要么全部不执行,回到原数据状态。

书面解释:事务具有原子性,一致性,隔离性,持久性。

  • 原子性:事务必须是一个自动工作的单元,要么全部执行,要么全部不执行。
  • 一致性:事务结束的时候,所有的内部数据都是正确的。
  • 隔离性:并发多个事务时,各个事务不干涉内部数据,处理的都是另外一个事务处理之前或之后的数据。
  • 持久性:事务提交之后,数据是永久性的,不可再回滚。

然而在SQL Server中事务被分为3类常见的事务:

  • 自动提交事务:是SQL Server默认的一种事务模式,每条Sql语句都被看成一个事务进行处理,你应该没有见过,一条Update 修改2个字段的语句,只修该了1个字段而另外一个字段没有修改。。
  • 显式事务:T-sql标明,由Begin Transaction开启事务开始,由Commit Transaction 提交事务、Rollback Transaction 回滚事务结束。
  • 隐式事务:使用Set IMPLICIT_TRANSACTIONS ON 将将隐式事务模式打开,不用Begin Transaction开启事务,当一个事务结束,这个模式会自动启用下一个事务,只用Commit Transaction 提交事务、Rollback Transaction 回滚事务即可。

显式事务的应用

常用语句就四个。

  • Begin Transaction:标记事务开始。
  • Commit Transaction:事务已经成功执行,数据已经处理妥当。
  • Rollback Transaction:数据处理过程中出错,回滚到没有处理之前的数据状态,或回滚到事务内部的保存点。
  • Save Transaction:事务内部设置的保存点,就是事务可以不全部回滚,只回滚到这里,保证事务内部不出错的前提下。

上面的都是心法,下面的给你来个招式,要看仔细啦。

复制代码
 1 ---开启事务
 2 begin tran
 3 --错误扑捉机制,看好啦,这里也有的。并且可以嵌套。
 4 begin try  
 5    --语句正确
 6    insert into lives (Eat,Play,Numb) values ('猪肉','足球',1)
 7    --Numb为int类型,出错
 8    insert into lives (Eat,Play,Numb) values ('猪肉','足球','abc')
 9    --语句正确
10    insert into lives (Eat,Play,Numb) values ('狗肉','篮球',2)
11 end try
12 begin catch
13    select Error_number() as ErrorNumber,  --错误代码
14           Error_severity() as ErrorSeverity,  --错误严重级别,级别小于10 try catch 捕获不到
15           Error_state() as ErrorState ,  --错误状态码
16           Error_Procedure() as ErrorProcedure , --出现错误的存储过程或触发器的名称。
17           Error_line() as ErrorLine,  --发生错误的行号
18           Error_message() as ErrorMessage  --错误的具体信息
19    if(@@trancount>0) --全局变量@@trancount,事务开启此值+1,他用来判断是有开启事务
20       rollback tran  ---由于出错,这里回滚到开始,第一条语句也没有插入成功。
21 end catch
22 if(@@trancount>0)
23 commit tran  --如果成功Lives表中,将会有3条数据。
24 
25 --表本身为空表,ID ,Numb为int 类型,其它为nvarchar类型
26 select * from lives
复制代码

复制代码
---开启事务
begin tran
--错误扑捉机制,看好啦,这里也有的。并且可以嵌套。
begin try    
   --语句正确
   insert into lives (Eat,Play,Numb) values ('猪肉','足球',1)   
    --加入保存点
   save tran pigOneIn
   --Numb为int类型,出错
   insert into lives (Eat,Play,Numb) values ('猪肉','足球',2)
   --语句正确
   insert into lives (Eat,Play,Numb) values ('狗肉','篮球',3)
end try
begin catch
   select Error_number() as ErrorNumber,  --错误代码
          Error_severity() as ErrorSeverity,  --错误严重级别,级别小于10 try catch 捕获不到
          Error_state() as ErrorState ,  --错误状态码
          Error_Procedure() as ErrorProcedure , --出现错误的存储过程或触发器的名称。
          Error_line() as ErrorLine,  --发生错误的行号
          Error_message() as ErrorMessage  --错误的具体信息
   if(@@trancount>0) --全局变量@@trancount,事务开启此值+1,他用来判断是有开启事务
      rollback tran   ---由于出错,这里回滚事务到原点,第一条语句也没有插入成功。
end catch
if(@@trancount>0)
rollback tran pigOneIn --如果成功Lives表中,将会有3条数据。

--表本身为空表,ID ,Numb为int 类型,其它为nvarchar类型
select * from lives
复制代码

使用set xact_abort

设置 xact_abort on/off , 指定是否回滚当前事务,为on时如果当前sql出错,回滚整个事务,为off时如果sql出错回滚当前sql语句,其它语句照常运行读写数据库。

需要注意的时:xact_abort只对运行时出现的错误有用,如果sql语句存在编译时错误,那么他就失灵啦。

复制代码
delete lives  --清空数据
set xact_abort off
begin tran 
    --语句正确
   insert into lives (Eat,Play,Numb) values ('猪肉','足球',1)   
   --Numb为int类型,出错,如果1234..那个大数据换成'132dsaf' xact_abort将失效
   insert into lives (Eat,Play,Numb) values ('猪肉','足球',12345646879783213)
   --语句正确
   insert into lives (Eat,Play,Numb) values ('狗肉','篮球',3)
commit tran
select * from lives
复制代码

为on时,结果集为空,因为运行是数据过大溢出出错,回滚整个事务。

事务把死锁给整出来啦

跟着做:打开两个查询窗口,把下面的语句,分别放入2个查询窗口,在5秒内运行2个事务模块。

begin tran 
  update lives set play='羽毛球'
  waitfor delay '0:0:5'  
  update dbo.Earth set Animal='老虎' 
commit tran
复制代码
begin tran 
  update Earth set Animal='老虎' 
  waitfor  delay '0:0:5' --等待5秒执行下面的语句
  update lives set play='羽毛球'
commit tran
select * from lives
select * from Earth
复制代码

为什么呢,下面我们看看锁,什么是锁。

并发事务成败皆归于锁——锁定

在多用户都用事务同时访问同一个数据资源的情况下,就会造成以下几种数据错误。

  • 更新丢失:多个用户同时对一个数据资源进行更新,必定会产生被覆盖的数据,造成数据读写异常。
  • 不可重复读:如果一个用户在一个事务中多次读取一条数据,而另外一个用户则同时更新啦这条数据,造成第一个用户多次读取数据不一致。
  • 脏读:第一个事务读取第二个事务正在更新的数据表,如果第二个事务还没有更新完成,那么第一个事务读取的数据将是一半为更新过的,一半还没更新过的数据,这样的数据毫无意义。
  • 幻读:第一个事务读取一个结果集后,第二个事务,对这个结果集经行增删操作,然而第一个事务中再次对这个结果集进行查询时,数据发现丢失或新增。

然而锁定,就是为解决这些问题所生的,他的存在使得一个事务对他自己的数据块进行操作的时候,而另外一个事务则不能插足这些数据块。这就是所谓的锁定。

锁定从数据库系统的角度大致可以分为6种:

  • 共享锁(S):还可以叫他读锁。可以并发读取数据,但不能修改数据。也就是说当数据资源上存在共享锁的时候,所有的事务都不能对这个资源进行修改,直到数据读取完成,共享锁释放。
  • 排它锁(X):还可以叫他独占锁、写锁。就是如果你对数据资源进行增删改操作时,不允许其它任何事务操作这块资源,直到排它锁被释放,防止同时对同一资源进行多重操作。
  • 更新锁(U):防止出现死锁的锁模式,两个事务对一个数据资源进行先读取在修改的情况下,使用共享锁和排它锁有时会出现死锁现象,而使用更新锁则可以避免死锁的出现。资源的更新锁一次只能分配给一个事务,如果需要对资源进行修改,更新锁会变成排他锁,否则变为共享锁。
  • 意向锁:SQL Server需要在层次结构中的底层资源上(如行,列)获取共享锁,排它锁,更新锁。例如表级放置了意向共享锁,就表示事务要对表的页或行上使用共享锁。在表的某一行上上放置意向锁,可以防止其它事务获取其它不兼容的的锁。意向锁可以提高性能,因为数据引擎不需要检测资源的每一列每一行,就能判断是否可以获取到该资源的兼容锁。意向锁包括三种类型:意向共享锁(IS),意向排他锁(IX),意向排他共享锁(SIX)。
  • 架构锁:防止修改表结构时,并发访问的锁。
  • 大容量更新锁:允许多个线程将大容量数据并发的插入到同一个表中,在加载的同时,不允许其它进程访问该表。

这些锁之间的相互兼容性,也就是,是否可以同时存在。

 现有的授权模式     
请求的模式ISSUIXSIXX
意向共享 (IS)
共享 (S)
更新 (U)
意向排他 (IX)
意向排他共享 (SIX)
排他 (X)

锁兼容性具体参见:http://msdn.microsoft.com/zh-cn/library/ms186396.aspx

锁粒度和层次结构参见:http://msdn.microsoft.com/zh-cn/library/ms189849(v=sql.105).aspx

 死锁

什么是死锁,为什么会产生死锁。我用 “事务把死锁给整出来啦” 标题下的两个事务产生的死锁来解释应该会更加生动形象点。

例子是这样的:

第一个事务(称为A):先更新lives表 —>>停顿5秒—->>更新earth表

第二个事务(称为B):先更新earth表—>>停顿5秒—->>更新lives表

先执行事务A—-5秒之内—执行事务B,出现死锁现象。

过程是这样子的:

  1. A更新lives表,请求lives的排他锁,成功。
  2. B更新earth表,请求earth的排他锁,成功。
  3. 5秒过后
  4. A更新earth,请求earth的排它锁,由于B占用着earth的排它锁,等待。
  5. B更新lives,请求lives的排它锁,由于A占用着lives的排它锁,等待。

这样相互等待对方释放资源,造成资源读写拥挤堵塞的情况,就被称为死锁现象,也叫做阻塞。而为什么会产生,上例就列举出来啦。

然而数据库并没有出现无限等待的情况,是因为数据库搜索引擎会定期检测这种状况,一旦发现有情况,立马选择一个事务作为牺牲品。牺牲的事务,将会回滚数据。有点像两个人在过独木桥,两个无脑的人都走在啦独木桥中间,如果不落水,必定要有一个人给退回来。这种相互等待的过程,是一种耗时耗资源的现象,所以能避则避。

哪个人会被退回来,作为牺牲品,这个我们是可以控制的。控制语法:

set deadlock_priority  <级别>

死锁处理的优先级别为 low<normal<high,不指定的情况下默认为normal,牺牲品为随机。如果指定,牺牲品为级别低的。

还可以使用数字来处理标识级别:-10到-5为low,-5为normal,-5到10为high。

减少死锁的发生,提高数据库性能

死锁耗时耗资源,然而在大型数据库中,高并发带来的死锁是不可避免的,所以我们只能让其变的更少。

  1. 按照同一顺序访问数据库资源,上述例子就不会发生死锁啦
  2. 保持是事务的简短,尽量不要让一个事务处理过于复杂的读写操作。事务过于复杂,占用资源会增多,处理时间增长,容易与其它事务冲突,提升死锁概率。
  3. 尽量不要在事务中要求用户响应,比如修改新增数据之后在完成整个事务的提交,这样延长事务占用资源的时间,也会提升死锁概率。
  4. 尽量减少数据库的并发量。
  5. 尽可能使用分区表,分区视图,把数据放置在不同的磁盘和文件组中,分散访问保存在不同分区的数据,减少因为表中放置锁而造成的其它事务长时间等待。
  6. 避免占用时间很长并且关系表复杂的数据操作。
  7. 使用较低的隔离级别,使用较低的隔离级别比使用较高的隔离级别持有共享锁的时间更短。这样就减少了锁争用。

可参考:http://msdn.microsoft.com/zh-cn/library/ms191242(v=sql.105).aspx

查看锁活动情况:

--查看锁活动情况
select * from sys.dm_tran_locks
--查看事务活动情况
dbcc opentran

可参考:http://msdn.microsoft.com/zh-cn/library/ms190345.aspx

为事务设置隔离级别

所谓事物隔离级别,就是并发事务对同一资源的读取深度层次。分为5种。

  • read uncommitted:这个隔离级别最低啦,可以读取到一个事务正在处理的数据,但事务还未提交,这种级别的读取叫做脏读。
  • read committed:这个级别是默认选项,不能脏读,不能读取事务正在处理没有提交的数据,但能修改。
  • repeatable read:不能读取事务正在处理的数据,也不能修改事务处理数据前的数据。
  • snapshot:指定事务在开始的时候,就获得了已经提交数据的快照,因此当前事务只能看到事务开始之前对数据所做的修改。
  • serializable:最高事务隔离级别,只能看到事务处理之前的数据。
--语法
set tran isolation level <级别>

read uncommitted隔离级别的例子:

begin tran 
  set deadlock_priority low
  update Earth set Animal='老虎' 
  waitfor  delay '0:0:5' --等待5秒执行下面的语句
rollback tran

开另外一个查询窗口执行下面语句

set tran isolation level read uncommitted
select * from Earth  --读取的数据为正在修改的数据 ,脏读
waitfor  delay '0:0:5'  --5秒之后数据已经回滚
select * from Earth  --回滚之后的数据

read committed隔离级别的例子:

begin tran 
  update Earth set Animal='老虎' 
  waitfor  delay '0:0:10' --等待5秒执行下面的语句
rollback tran
set tran isolation level read committed
select * from Earth ---获取不到老虎,不能脏读
update Earth set Animal='猴子1'   --可以修改
waitfor  delay '0:0:10'  --10秒之后上一个事务已经回滚
select * from Earth  --修改之后的数据,而不是猴子

剩下的几个级别,不一一列举啦,自己理解吧。

设置锁超时时间

发生死锁的时候,数据库引擎会自动检测死锁,解决问题,然而这样子是很被动,只能在发生死锁后,等待处理。

然而我们也可以主动出击,设置锁超时时间,一旦资源被锁定阻塞,超过设置的锁定时间,阻塞语句自动取消,释放资源,报1222错误。

好东西一般都具有两面性,调优的同时,也有他的不足之处,那就是一旦超过时间,语句取消,释放资源,但是当前报错事务,不会回滚,会造成数据错误,你需要在程序中捕获1222错误,用程序处理当前事务的逻辑,使数据正确。

--查看超时时间,默认为-1
select @@lock_timeout
--设置超时时间
set lock_timeout 0 --为0时,即为一旦发现资源锁定,立即报错,不在等待,当前事务不回滚,设置时间需谨慎处理后事啊,你hold不住的。

查看与杀死锁和进程

复制代码
--检测死锁
--如果发生死锁了,我们怎么去检测具体发生死锁的是哪条SQL语句或存储过程?
--这时我们可以使用以下存储过程来检测,就可以查出引起死锁的进程和SQL语句。SQL Server自带的系统存储过程sp_who和sp_lock也可以用来查找阻塞和死锁, 但没有这里介绍的方法好用。 

use master
go
create procedure sp_who_lock
as
begin
declare @spid int,@bl int,
 @intTransactionCountOnEntry  int,
        @intRowcount    int,
        @intCountProperties   int,
        @intCounter    int

 create table #tmp_lock_who (
 id int identity(1,1),
 spid smallint,
 bl smallint)
 
 IF @@ERROR<>0 RETURN @@ERROR
 
 insert into #tmp_lock_who(spid,bl) select  0 ,blocked
   from (select * from sysprocesses where  blocked>0 ) a 
   where not exists(select * from (select * from sysprocesses where  blocked>0 ) b 
   where a.blocked=spid)
   union select spid,blocked from sysprocesses where  blocked>0

 IF @@ERROR<>0 RETURN @@ERROR 
  
-- 找到临时表的记录数
 select  @intCountProperties = Count(*),@intCounter = 1
 from #tmp_lock_who
 
 IF @@ERROR<>0 RETURN @@ERROR 
 
 if @intCountProperties=0
  select '现在没有阻塞和死锁信息' as message

-- 循环开始
while @intCounter <= @intCountProperties
begin
-- 取第一条记录
  select  @spid = spid,@bl = bl
  from #tmp_lock_who where Id = @intCounter 
 begin
  if @spid =0 
            select '引起数据库死锁的是: '+ CAST(@bl AS VARCHAR(10)) + '进程号,其执行的SQL语法如下'
 else
            select '进程号SPID:'+ CAST(@spid AS VARCHAR(10))+ '被' + '进程号SPID:'+ CAST(@bl AS VARCHAR(10)) +'阻塞,其当前进程执行的SQL语法如下'
 DBCC INPUTBUFFER (@bl )
 end 

-- 循环指针下移
 set @intCounter = @intCounter + 1
end

drop table #tmp_lock_who

return 0
end
 

--杀死锁和进程
--如何去手动的杀死进程和锁?最简单的办法,重新启动服务。但是这里要介绍一个存储过程,通过显式的调用,可以杀死进程和锁。

use master
go

if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[p_killspid]') and OBJECTPROPERTY(id, N'IsProcedure') = 1)
drop procedure [dbo].[p_killspid]
GO

create proc p_killspid
@dbname varchar(200)    --要关闭进程的数据库名
as  
    declare @sql  nvarchar(500)  
    declare @spid nvarchar(20)

    declare #tb cursor for
        select spid=cast(spid as varchar(20)) from master..sysprocesses where dbid=db_id(@dbname)
    open #tb
    fetch next from #tb into @spid
    while @@fetch_status=0
    begin  
        exec('kill '+@spid)
        fetch next from #tb into @spid
    end  
    close #tb
    deallocate #tb
go

--用法  
exec p_killspid  'newdbpy' 

--查看锁信息
--如何查看系统中所有锁的详细信息?在企业管理管理器中,我们可以看到一些进程和锁的信息,这里介绍另外一种方法。
--查看锁信息
create table #t(req_spid int,obj_name sysname)

declare @s nvarchar(4000)
    ,@rid int,@dbname sysname,@id int,@objname sysname

declare tb cursor for 
    select distinct req_spid,dbname=db_name(rsc_dbid),rsc_objid
    from master..syslockinfo where rsc_type in(4,5)
open tb
fetch next from tb into @rid,@dbname,@id
while @@fetch_status=0
begin
    set @s='select @objname=name from ['+@dbname+']..sysobjects where id=@id'
    exec sp_executesql @s,N'@objname sysname out,@id int',@objname out,@id
    insert into #t values(@rid,@objname)
    fetch next from tb into @rid,@dbname,@id
end
close tb
deallocate tb

select 进程id=a.req_spid
    ,数据库=db_name(rsc_dbid)
    ,类型=case rsc_type when 1 then 'NULL 资源(未使用)'
        when 2 then '数据库'
        when 3 then '文件'
        when 4 then '索引'
        when 5 then '表'
        when 6 then '页'
        when 7 then '键'
        when 8 then '扩展盘区'
        when 9 then 'RID(行 ID)'
        when 10 then '应用程序'
    end
    ,对象id=rsc_objid
    ,对象名=b.obj_name
    ,rsc_indid
 from master..syslockinfo a left join #t b on a.req_spid=b.req_spid

go
drop table #t
复制代码

 

仔细阅读,希望能分享给你一点点东西,谢谢,over。

SQL Server中的事务与锁 – 张龙豪 – 博客园已关闭评论
2019年1月11日 By mikel 分类: 数据库
http://baa.im/847971

出现名为’MM_CURSOR’ 的游标已存在。一般出现这样的问题是:1:游标没有 –关闭 释放如:SQL代码–关闭游标CLOSEMM_CURSOR–释放游标DEALLOCATEM

来源: 解决为’*********’ 的游标已存在问题 – 挑战 – 博客园

出现名为’MM_CURSOR’ 的游标已存在。 
一般出现这样的问题是: 
1:游标没有    –关闭 释放 
如: 

SQL代码 
  1. –关闭游标
  2.       CLOSE MM_CURSOR
  3.         –释放游标
  4.      DEALLOCATE MM_CURSOR

2:游标已存在同名情况,此时就需要在定义游标时申明一个局部的游标 
如: 

SQL代码 
  1. /*检索已经配置好的新村镇的所有乡级部门*/
  2.    —申明游标
  3. DECLARE deptCursor CURSOR
  4.    local FOR
  5.    SELECT  deptname, deptsimplename,distid, deptuncode,deptqueryno,ifreport,deptsort,enable,deptfloor,deptcharacter,caseSMSFlag,deptType
  6.    FROM t_department
  7.    where  PARENTID=250 and deptType=‘2’

其实我的情况都不是这样,只是在使用嵌套多层循环操作时把两个游标全部放在存储过程末后 

Sql代码 
  1. –关闭游标
  2.       CLOSE MM_CURSOR
  3.         –释放游标
  4.      DEALLOCATE MM_CURSOR
  5.    –关闭游标–释放游标
  6. CLOSE deptCursor
  7.    –释放游标
  8. DEALLOCATE deptCursor

没有及时关闭导致问题出现! 
正确代码如下 

Sql代码 
  1. set ANSI_NULLS ON
  2. set QUOTED_IDENTIFIER ON
  3. go
  4. —drop  PROCEDURE copyDept
  5. ALTER PROCEDURE [dbo].[copyDept]
  6.     as
  7.     declare @deptCode varchar(20)
  8.     declare @deptname varchar(10)
  9.     declare @deptsimplename varchar(100)
  10.     declare @distid bigint
  11.     declare @deptuncode varchar(100)
  12.     declare @deptqueryno varchar(100)
  13.     declare @ifreport varchar(4)
  14.     declare @deptsort int
  15.     declare @enable varchar(6)
  16.     declare @deptfloor smallint
  17.     declare @deptcharacter varchar(50)
  18.     declare @caseSMSFlag varchar(4)
  19.     declare @deptType varchar(1)
  20.     declare @DeNo bigint
  21.     set nocount on
  22.     begin
  23.     set  @deptcode = ‘2000000’
  24.     /*检索已经配置好的新村镇的所有乡级部门*/
  25.     —申明游标
  26.     DECLARE deptCursor CURSOR
  27.     local FOR
  28.     SELECT  deptname, deptsimplename,distid, deptuncode,deptqueryno,ifreport,deptsort,enable,deptfloor,deptcharacter,caseSMSFlag,deptType
  29.     FROM t_department
  30.     where  PARENTID=250 and deptType=‘2’
  31.     —打开游标
  32.     OPEN deptCursor
  33.     –循环取出游标    
  34.     FETCH NEXT FROM deptCursor INTO @deptname,@deptsimplename,@distid,@deptuncode,@deptqueryno,@ifreport,@deptsort,@enable,@deptfloor,@deptcharacter,@caseSMSFlag,@deptType
  35.     while (@@FETCH_STATUS = 0)
  36.         begin
  37.             /*检索乡镇行政部门:如赵集镇,龙王乡…*/
  38.             —申明游标
  39.             Declare MM_CURSOR CURSOR
  40.             local  FOR
  41.             Select DEPTID from t_department where  ENABLE= ‘启用’ and DISTID = 1 and deptType=0 and deptid !=250—demo,except 250 — and PARENTID =288–and deptid not in (243,244)–and is_convenience=@tjType jc_dreaming
  42.             Order by DEPTCODE /**ONLY VALID DEPARTMENT */
  43.             — 打开游标
  44.             open MM_CURSOR
  45.             –循环取出游标    
  46.             FETCH NEXT FROM MM_CURSOR  INTO @DeNo
  47.             while (@@FETCH_STATUS = 0)
  48.                 BEGIN
  49.                  set @deptcode = convert(varchar(20),cast(@deptcode as int)+1)
  50.                 print(@deptcode)
  51.                  INSERT INTO T_DEPARTMENT (deptcode, deptname, deptsimplename,distid, deptuncode,deptqueryno,ifreport,deptsort,enable,deptfloor,PARENTID,deptcharacter,caseSMSFlag,deptType)
  52.                                     VALUES (@deptcode,@deptname,@deptsimplename,@distid,@deptuncode,@deptqueryno,@ifreport,@deptsort,@enable,@deptfloor,@DeNo,@deptcharacter,@caseSMSFlag,@deptType)
  53.                 FETCH NEXT FROM MM_CURSOR INTO @DeNo
  54.                 END
  55.             –关闭游标
  56.           CLOSE MM_CURSOR
  57.          –释放游标
  58.          DEALLOCATE MM_CURSOR
  59.         FETCH NEXT FROM deptCursor INTO @deptname,@deptsimplename,@distid,@deptuncode,@deptqueryno,@ifreport,@deptsort,@enable,@deptfloor,@deptcharacter,@caseSMSFlag,@deptType
  60.                                         –@deptname,@deptsimplename,@distid,@deptuncode,@deptqueryno,@ifreport,@deptsort,@enable,@deptfloor,@deptcharacter,@caseSMSFlag,@deptType
  61.         end
  62.     end
  63.     –关闭游标
  64.     CLOSE deptCursor
  65.     –释放游标
  66.     DEALLOCATE deptCursor

此外,在刚开始调用存储过程还遇到一个问题:程序处于正在查询状态,近一个小时,我想,数据还没那么复杂,可能出现死循环或某个游标没有移动… 
可是看了代码,没有出现这样的情况, 
经同事指点: 

Sql代码 
  1. —申明游标
  2.             Declare MM_CURSOR CURSOR
  3.             local  FOR
  4.             Select DEPTID from t_department where  ENABLE= ‘启用’ and DISTID = 1 and deptType=0 and deptid !=250—demo,except 250 — and PARENTID =288–and deptid not in (243,244)–and is_convenience=@tjType jc_dreaming
  5.             Order by DEPTCODE /**ONLY VALID DEPARTMENT */
  6.             — 打开游标
  7.             open MM_CURSOR
  8.             –循环取出游标    
  9.             FETCH NEXT FROM MM_CURSOR  INTO @DeNo
  10.             while (@@FETCH_STATUS = 0)
  11.                 set @deptcode = convert(varchar(20),cast(@deptcode as int)+1)   //把此行代码移至begin代码内即可
  12.                 BEGIN
  13.                 print(@deptcode)
  14.                  INSERT INTO T_DEPARTMENT (deptcode, deptname, deptsimplename,distid, deptuncode,deptqueryno,ifreport,deptsort,enable,deptfloor,PARENTID,deptcharacter,caseSMSFlag,deptType)
  15.                                     VALUES (@deptcode,@deptname,@deptsimplename,@distid,@deptuncode,@deptqueryno,@ifreport,@deptsort,@enable,@deptfloor,@DeNo,@deptcharacter,@caseSMSFlag,@deptType)
  16.                 FETCH NEXT FROM MM_CURSOR INTO @DeNo
  17.                 END
  18.             –关闭游标
  19.           CLOSE MM_CURSOR
  20.          –释放游标
  21.          DEALLOCATE MM_CURSOR
解决为’*********’ 的游标已存在问题 – 挑战 – 博客园已关闭评论
2019年1月11日 By mikel 分类: C#, 数据库
http://baa.im/847971

来源: SQLSERVER2005调试存储过程跟函数 – 桦仔 – 博客园

SQLServer2005调试存储过程跟函数

以前一直用Toad for SQLServer 和Database.Net 来调试SQL2005的存储过程跟函数

Toad for SQLServer介绍以及下载:toad for sqlserver5.7

Database.Net 介绍以及下载:Database .NET 8.0 发布,数据库管理工具

今日发现原来上面两个工具的功能,微软的Visual Studio也可以做到

本来微软自家的Visual Studio就支持调试SQLSERVER的存储过程跟函数,我竟然用了第三方工具,而且这些第三方工具也不是很好用

微软在SQLSERVER Management Studio2008里才集成了TSQL调试功能,那么SQL2005只有用VS来调试了


幸亏VS调试TSQL也不是很复杂

我的环境:VS2008 TeamSystem  ,  SQL2005 个人开发版 ,   Windows7 Ultimate

我使用的工具还是比较落后

step1:打开Visual Studio2008,然后点击菜单 视图-》服务器资源管理器

最好以管理员身份登录操作系统,打开VS的时候一般就以管理员的身份运行了

step2:在数据连接那里 右键-》添加连接

step3:数据源那里选择SQLSERVER就可以了

step4:选择你本地的数据库不要选择局域网或者广域网里的数据库,因为Windows安全问题的原因你是调试不了的只能执行

joe是我的笔记本电脑的计算机名,joe-homepc是局域网里另一台计算机的计算机名

 

选择好了之后点击“测试连接” ,没有问题就可以点击“确定”按钮了

 

step5:打开存储过程或者函数的树节点,然后找一个存储过程或者函数进行调试

step6:调试函数,调试的时候无论是存储过程还是函数,都会直接跳过变量定义部分进入函数或存储过程主体来执行

只有当TSQL语句运行的时候才会中断到断点的地方

step7:调试存储过程

 

当你调试远程SQL服务器的时候,VS就会报错,就像你写C#的时候是调试不了远程电脑上的代码的

 

不过个人觉得调试如果能看到表数据就最好了,调试的时候varchar nvarchar int  datetime这些数据都可以看到

但是表的内容,无论是临时表,表变量,表都是无法看到数据的,如果你想查看中途我插入到@temp表变量里的数据是看不到的

包括Toad for SqlServer 这些工具也是没办法看到的,个人觉得这是一个缺点


最后给大家一个函数,这个函数的作用就是切割字符串,大家可以用来调试一下

复制代码
 1 CREATE     FUNCTION [dbo].[SplitString] (@string varchar(6000))
 2 RETURNS @temp TABLE(substr varchar(30))
 3 AS
 4 begin
 5    -- 用法 select * from SplitString('1, 2, 3')
 6 
 7    declare @str varchar(6000)
 8    declare @i int
 9 
10    set @i=0
11    while(@i<len(@string))
12    begin    
13        if(substring(@string,@i,1)=',')
14         begin
15             select @str=substring(@string,1,@i-1)
16             set @string=substring(@string,@i+1,len(@string))
17                   set @i=1
18             insert into @temp values(ltrim(@str))
19     
20            end
21        else
22            begin 
23             set @i=@i+1
24            end
25    end
26 
27    insert into @temp values(ltrim(@string))
28 
29    RETURN
30 end
复制代码

 

SQLSERVER2005调试存储过程跟函数 – 桦仔 – 博客园已关闭评论
2019年1月10日 By mikel 分类: 数据库
http://baa.im/847971

来源: SQL中MAX()和MIN()函数的使用 – cherry.zhou – 博客园

SQL数据库中,最大/最小值函数—MAX()/MIN()是经常要用到的,下面就将为您分别介绍MAX()函数和MIN()函数的使用,供您参考,希望对您学习SQL数据库能有些帮助。

当需要了解一列中的最大值时,可以使用MAX()函数;同样,当需要了解一列中的最小值时,可以使用MIN()函数。语法如下。

SELECT          MAX (column_name) / MIN (column_name)

FROM            table_name

说明:列column_name中的数据可以是数值、字符串或是日期时间数据类型。MAX()/MIN()函数将返回与被传递的列同一数据类型的单一值。

实例7 MAX()函数的使用

查询TEACHER表中教师的最大年龄。实例代码:

SELECT MAX (AGE) AS MAXAGE

FROM     TEACHER

运行结果如图1示。

图1

图1TEACHER表中教师的最大年龄

 

实例8 在WHERE子句中使用子查询返回最大值

查询TEACHER表中年纪最大的教师的教工号、姓名、性别等信息。

实例代码:

SELECT TNAME, DNAME, TSEX, SAL, AGE

FROM     TEACHER

WHERE   AGE=(SELECT MAX (AGE) FROM    TEACHER)

运行结果如图2示。

图2

图2在WHERE子句中使用子查询返回最大值

 

MAX()和MIN()函数不仅可以作用于数值型数据,也可以作用于字符串或是日期时间数据类型的数据。

实例MAX()函数用于字符型数据

如下面代码:

SELECT MAX (TNAME) AS MAXNAME

FROM     TEACHER

运行结果如图3示。

图3

图3在字符串数据类型中使用MAX的结果

可见,对于字符串也可以求其最大值。

说明:对字符型数据的最大值,是按照首字母由A~Z的顺序排列,越往后,其值越大。当然,对于汉字则是按照其全拼拼音排列的,若首字符相同,则比较下一个字符,以此类推。

当然,对与日期时间类型的数据也可以求其最大/最小值,其大小排列就是日期时间的早晚,越早认为其值越小,如下面的实例。

实例 MAX()、MIN()函数用于时间型数据

从COURSE表中查询最早和最晚考试课程的考试时间。实例代码:

SELECT MIN (CTEST) AS EARLY_DATE,

MAX (CTEST) AS LATE_DATE

FROM     COURSE

运行结果如图4示。

图4

图4COURSE表中最早和最晚考试课程的考试时间

可见,返回结果的数据类型与该列定义的数据类型相同。

注意:确定列中的最大值(最小值)时,MAX( )(MIN( ))函数忽略NULL值。但是,如果在该列中,所有行的值都是NULL,则MAX( )/MIN( )函数将返回NULL值。

SQL中MAX()和MIN()函数的使用 – cherry.zhou – 博客园已关闭评论
2019年1月8日 By mikel 分类: JavaScript, PHP
http://baa.im/847971

声明: 本篇博文只是个人工作中的分享总结,仅代表个人观点,虽然解决了不少网友的问题,但同时也引来了一些网友的不满,所以特此声明,当您遇到本博文解决不了的问题,可以尝试重新进行其他搜索或者一起交流,相信

来源: 关于微信二次分享,标题变链接的解决方法(二)—-代码部分 – joshua317 – 博客园

上篇主要介绍了分享的文档,下面这篇直接上代码(代码是用PHP来写的),让大家更容易理解。上篇地址:关于微信二次分享,描述变链接的解决方法(一)—-文档说明

代码分三部分进行,第一部分是C层(Controller),第二部分是M层(Model),第三部分是V层(View)

1.控制器层WeixinController

 

复制代码
$data = array(
        'pic'=>'',//图片地址
        'title'=>'',//标题
        'description'=>''//描述
);
$weixinmodel = new WeixinshareModel();
$weixindata = array();
$weixindata['appId'] = 'xxxxxxxxxxxxxxxxxxxx';//appid
$weixindata['nonceStr'] = $weixinmodel->createNonceStr();
$weixindata['timestamp'] = time();

$protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' || $_SERVER['SERVER_PORT'] == 443) ? "https://" : "http://";
$url = "$protocol$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]";

$weixindata['pic'] = $data['pic'];

$weixindata['link'] = $url;

$weixindata['title'] = $data['title'];

$weixindata['description'] = mb_substr($data['description'], 0, 30, 'UTF-8');

$weixindata['signature'] = $weixinmodel->signature($weixindata['nonceStr'],$weixindata['timestamp'],$url);

$this->assign("weixindata", $weixindata);
复制代码

 

2.模型层WeixinModel

复制代码
<?php
class WeixinshareModel extends BaseModel
{
    const APPID = 'xxxxxxxxxxxxxxx';
    const APPSECRET = 'xxxxxxxxxxxxxxxxxxxxxxxx';
    function __construct()
    {
        parent::__construct();
    }

    //获取access_token
    public function accessToken()
    {
        $access_token = $this->redis->get("weixin_access_token");//存入redis,这里要结合自己的项目,对redis或者memcahe进行设置
        if(!$access_token){
            $url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=".self::APPID."&secret=".self::APPSECRET;
            $data = json_decode($this->httpGet($url),true);
            if(isset($data['access_token']) && $data['access_token'] != ''){
                $access_token = $this->redis->set("weixin_access_token",$data['access_token'],7200);
            }else{
                return false;
            }
        }
        return $access_token;
    }

    //用第一步拿到的access_token 采用http GET方式请求获得jsapi_ticket
    public function jsapiTicket()
    {
        $jsapi_ticket = $this->redis->get("weixin_jsapi_ticket");
        if(!$jsapi_ticket){
            $access_token = $this->accessToken();
            if($access_token){
                $url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=".$access_token."&type=jsapi";
                $data = json_decode($this->httpGet($url),true);
                if(isset($data['errcode']) && $data['errcode']== 0){//请求成功
                    $jsapi_ticket = $this->redis->set("weixin_jsapi_ticket",$data['ticket'],7200);
                }else{
                    return false;
                }
            }else{
                return false;
            }
        }
        return $jsapi_ticket;
    }


    //获取signature
    public function signature($nonceStr,$timestamp,$url)
    {
        $jsapi_ticket = $this->jsapiTicket();

        $signature = '';
        if($jsapi_ticket) {
            $string = "jsapi_ticket=$jsapi_ticket&noncestr=$nonceStr&timestamp=$timestamp&url=$url";
            $signature = sha1($string);//对string1进行sha1签名,得到signature
        }else{
            return false;
        }
        return $signature;
    }

  //生成随机字符串
    public function createNonceStr($length = 16) {
        $chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
        $str = "";
        for ($i = 0; $i < $length; $i++) {
            $str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
        }
        return $str;
    }
  

  //远程请求,可以调用自己项目的远程请求
    public function httpGet($url)
    {
        $curl = curl_init();
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($curl, CURLOPT_TIMEOUT, 500);
        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
        curl_setopt($curl, CURLOPT_URL, $url);

        $res = curl_exec($curl);
        curl_close($curl);

        return $res;
    }

}
复制代码

 

3.视图层weixin.phtml

复制代码
<!DOCTYPE html>
<html>
<head>
    <title></title>
    <meta charset="utf-8">
    <meta content="width=device-width,minimum-scale=1.0,maximum-scale=1.0" name="viewport">
    <!--这个js必须引入-->
    <script type="text/javascript" src="http://res.wx.qq.com/open/js/jweixin-1.2.0.js"></script>
</head>
<body>
<script type="text/javascript">
    wx.config({
        //debug: true, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
        appId: '<?=$weixindata['appId']?>', // 必填,公众号的唯一标识
        timestamp: <?=$weixindata['timestamp']?>, // 必填,生成签名的时间戳
        nonceStr: '<?=$weixindata['nonceStr']?>', // 必填,生成签名的随机串
        signature: '<?=$weixindata['signature']?>',// 必填,签名,见附录1
        jsApiList: ['onMenuShareTimeline','onMenuShareAppMessage','onMenuShareQQ','onMenuShareWeibo','onMenuShareQZone'] // 必填,需要使用的JS接口列表
    });
    wx.ready(function(){
        wx.onMenuShareTimeline({//分享到朋友圈
            title: '<?=$weixindata['title']?>', // 分享标题
            link: '<?=$weixindata['link']?>', // 分享链接
            imgUrl: '<?=$weixindata['pic']?>' // 分享图标
        });
        wx.onMenuShareAppMessage({//分享给朋友
            title: '<?=$weixindata['title']?>', // 分享标题
            desc: '<?=$weixindata['description']?>', // 分享描述
            link: '<?=$weixindata['link']?>', // 分享链接
            imgUrl: '<?=$weixindata['pic']?>', // 分享图标
            type: '', // 分享类型,music、video或link,不填默认为link
            dataUrl: '', // 如果type是music或video,则要提供数据链接,默认为空
            success: function () {
                // 用户确认分享后执行的回调函数
            },
            cancel: function () {
                // 用户取消分享后执行的回调函数
            }
        });
        wx.onMenuShareQQ({//分享到QQ
            title: '<?=$weixindata['title']?>', // 分享标题
            desc: '<?=$weixindata['description']?>', // 分享描述
            link: '<?=$weixindata['link']?>', // 分享链接
            imgUrl: '<?=$weixindata['pic']?>' // 分享图标
        });
        wx.onMenuShareWeibo({//分享到腾讯微博
            title: '<?=$weixindata['title']?>', // 分享标题
            desc: '<?=$weixindata['description']?>', // 分享描述
            link: '<?=$weixindata['link']?>', // 分享链接
            imgUrl: '<?=$weixindata['pic']?>' // 分享图标
        });
        wx.onMenuShareQZone({//分享到QQ空间
            title: '<?=$weixindata['title']?>', // 分享标题
            desc: '<?=$weixindata['description']?>', // 分享描述
            link: '<?=$weixindata['link']?>', // 分享链接
            imgUrl: '<?=$weixindata['pic']?>' // 分享图标
        });

        // config信息验证后会执行ready方法,所有接口调用都必须在config接口获得结果之后,config是一个客户端的异步操作,所以如果需要在页面加载时就调用相关接口,则须把相关接口放在ready函数中调用来确保正确执行。对于用户触发时才调用的接口,则可以直接调用,不需要放在ready函数中。
    });

</script>
<div class="wrap">
    
</div>
</body>
</html>
复制代码

 

以上就是开发过程中的代码部分,php部分的代码没有优化,不足之处还望指正,若有疑惑之处可以QQ我:991769422;

关于微信二次分享,标题变链接的解决方法(二)—-代码部分 – joshua317 – 博客园已关闭评论
2019年1月8日 By mikel 分类: JavaScript
http://baa.im/847971

来源: 关于微信二次分享,描述变链接的解决方法(一)—-文档说明 – joshua317 – 博客园

声明:

本篇博文只是个人工作中的分享总结,仅代表个人观点,虽然解决了不少网友的问题,但同时也引来了一些网友的不满,所以特此声明,当您遇到本博文解决不了的问题,可以尝试重新进行其他搜索或者一起交流,相信总归能解决,而不是言语攻击!该博文的解决方案毕竟不是万金油,解决不了所有问题!

前言:

最近工作中遇到了使用微信二次分享的时候,标题被截短,描述也变成了链接,图片也没有,运营人员半夜还在嚷嚷,无奈只好硬着头皮去百度,去google,但是悲催的是没有详细的解决方法,最终只能自己去研究,还好最终搞出来了,决定分享一下,帮助需要的人。博文,分两篇,第一篇主要是微信的官方文档说明第二篇主要是代码部分

 

一、微信JS-SDK说明文档

1.概述

微信JS-SDK是微信公众平台面向网页开发者提供的基于微信内的网页开发工具包。

通过使用微信JS-SDK,网页开发者可借助微信高效地使用拍照、选图、语音、位置等手机系统的能力,同时可以直接使用微信分享、扫一扫、卡券、支付等微信特有的能力,为微信用户提供更优质的网页体验。

此文档面向网页开发者介绍微信JS-SDK如何使用及相关注意事项

2.JSSDK使用步骤

  2.1步骤一:绑定域名

先登录微信公众平台进入“公众号设置”的“功能设置”里填写“JS接口安全域名”,可以按照提示进行填写,最多填写三个。

备注:登录后可在“开发者中心”查看对应的接口权限,只有获取了权限才能保证后面的工作继续进行。

 

     2.2 步骤二:引入JS文件

在需要调用JS接口的页面引入如下JS文件,(支持https):http://res.wx.qq.com/open/js/jweixin-1.2.0.js

备注:支持使用 AMD/CMD 标准模块加载方法加载

  2.3 步骤三:通过config接口注入权限验证配置

  所有需要使用JS-SDK的页面必须先注入配置信息,否则将无法调用(同一个url仅需调用一次,对于变化url的SPA的web app可在每次url变化时进行调用,目前Android微信客      户端不支持pushState的H5新特性,所以使用pushState来实现web app的页面会导致签名失败,此问题会在Android6.2中修复)。

 

复制代码
wx.config({
    debug: true, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
    appId: '', // 必填,公众号的唯一标识
    timestamp: , // 必填,生成签名的时间戳
    nonceStr: '', // 必填,生成签名的随机串
    signature: '',// 必填,签名,见附录1
    jsApiList: [] // 必填,需要使用的JS接口列表,所有JS接口列表见附录2
});
复制代码

 

  2.4 步骤四:通过ready接口处理成功验证

wx.ready(function(){

    // config信息验证后会执行ready方法,所有接口调用都必须在config接口获得结果之后,config是一个客户端的异步操作,所以如果需要在页面加载时就调用相关接口,则须把相关接口放在ready函数中调用来确保正确执行。对于用户触发时才调用的接口,则可以直接调用,不需要放在ready函数中。
});

  

  2.5 步骤五:通过error接口处理失败验证

wx.error(function(res){

    // config信息验证失败会执行error函数,如签名过期导致验证失败,具体错误信息可以打开config的debug模式查看,也可以在返回的res参数中查看,对于SPA可以在这里更新签名。

});

 

3.接口调用说明

所有接口通过wx对象(也可使用jWeixin对象)来调用,参数是一个对象,除了每个接口本身需要传的参数之外,还有以下通用参数:

  1. success:接口调用成功时执行的回调函数。
  2. fail:接口调用失败时执行的回调函数。
  3. complete:接口调用完成时执行的回调函数,无论成功或失败都会执行。
  4. cancel:用户点击取消时的回调函数,仅部分有用户取消操作的api才会用到。
  5. trigger: 监听Menu中的按钮点击时触发的方法,该方法仅支持Menu中的相关接口。

备注:不要尝试在trigger中使用ajax异步请求修改本次分享的内容,因为客户端分享操作是一个同步操作,这时候使用ajax的回包会还没有返回。

以上几个函数都带有一个参数,类型为对象,其中除了每个接口本身返回的数据之外,还有一个通用属性errMsg,其值格式如下:

  1. 调用成功时:”xxx:ok” ,其中xxx为调用的接口名
  2. 用户取消时:”xxx:cancel”,其中xxx为调用的接口名
  3. 调用失败时:其值为具体错误信息

4.基础接口(只列出了使用到的接口)

  4.1判断当前客户端版本是否支持指定JS接口(测试的时候使用很不错)

  备注:checkJsApi接口是客户端6.0.2新引入的一个预留接口,第一期开放的接口均可不使用checkJsApi来检测。

复制代码
wx.checkJsApi({
    jsApiList: ['chooseImage'], // 需要检测的JS接口列表,所有JS接口列表见附录2,
    success: function(res) {
        // 以键值对的形式返回,可用的api值true,不可用为false
        // 如:{"checkResult":{"chooseImage":true},"errMsg":"checkJsApi:ok"}
    }
});
复制代码

5.分享接口

  5.1 获取“分享到朋友圈”按钮点击状态及自定义分享内容接口

复制代码
wx.onMenuShareTimeline({
    title: '', // 分享标题
    link: '', // 分享链接
    imgUrl: '', // 分享图标
    success: function () { 
        // 用户确认分享后执行的回调函数
    },
    cancel: function () { 
        // 用户取消分享后执行的回调函数
    }
});
复制代码

  5.2 获取“分享给朋友”按钮点击状态及自定义分享内容接口

复制代码
wx.onMenuShareAppMessage({
    title: '', // 分享标题
    desc: '', // 分享描述
    link: '', // 分享链接
    imgUrl: '', // 分享图标
    type: '', // 分享类型,music、video或link,不填默认为link
    dataUrl: '', // 如果type是music或video,则要提供数据链接,默认为空
    success: function () { 
        // 用户确认分享后执行的回调函数
    },
    cancel: function () { 
        // 用户取消分享后执行的回调函数
    }
});
复制代码

  5.3 获取“分享到QQ”按钮点击状态及自定义分享内容接口

复制代码
wx.onMenuShareQQ({
    title: '', // 分享标题
    desc: '', // 分享描述
    link: '', // 分享链接
    imgUrl: '', // 分享图标
    success: function () { 
       // 用户确认分享后执行的回调函数
    },
    cancel: function () { 
       // 用户取消分享后执行的回调函数
    }
});
复制代码

  5.4 获取“分享到腾讯微博”按钮点击状态及自定义分享内容接口

复制代码
wx.onMenuShareWeibo({
    title: '', // 分享标题
    desc: '', // 分享描述
    link: '', // 分享链接
    imgUrl: '', // 分享图标
    success: function () { 
       // 用户确认分享后执行的回调函数
    },
    cancel: function () { 
        // 用户取消分享后执行的回调函数
    }
});
复制代码

  5.5 获取“分享到QQ空间”按钮点击状态及自定义分享内容接口

复制代码
wx.onMenuShareQZone({
    title: '', // 分享标题
    desc: '', // 分享描述
    link: '', // 分享链接
    imgUrl: '', // 分享图标
    success: function () { 
       // 用户确认分享后执行的回调函数
    },
    cancel: function () { 
        // 用户取消分享后执行的回调函数
    }
});
复制代码

 

附录1-JS-SDK使用权限签名算法

  首先获取jsapi_ticket

生成签名之前必须先了解一下jsapi_ticket,jsapi_ticket是公众号用于调用微信JS接口的临时票据。正常情况下,jsapi_ticket的有效期为7200秒,

通过access_token来获取。由于获取jsapi_ticket的api调用次数非常有限,频繁刷新jsapi_ticket会导致api调用受限,影响自身业务,

开发者必须在自己的服务全局缓存jsapi_ticket 。

  1. 参考以下文档获取access_token(有效期7200秒,开发者必须在自己的服务全局缓存access_token):获取access token的方法

  2. 用第一步拿到的access_token 采用http GET方式请求获得jsapi_ticket(有效期7200秒,开发者必须在自己的服务全局缓存jsapi_ticket):https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi

成功返回如下JSON:

复制代码
{
"errcode":0,
"errmsg":"ok",
"ticket":"bxLdikRXVbTPdHSM05e5u5sUoXNKd8-41ZO3MhKoyN5OfkWITDGgnr2fwJ0m9E8NYzWKVZvdVtaUgWvsdshFKA",
"expires_in":7200
}
复制代码

获得jsapi_ticket之后,就可以生成JS-SDK权限验证的签名了。

其次获取签名,签名的算法如下:

 

签名生成规则如下:参与签名的字段包括noncestr(随机字符串), 有效的jsapi_ticket, timestamp(时间戳), url(当前网页的URL,不包含#及其后面部分) 。

对所有待签名参数按照字段名的ASCII 码从小到大排序(字典序)后,使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串string1。

这里需要注意的是所有参数名均为小写字符。对string1作sha1加密,字段名和字段值都采用原始值,不进行URL 转义。

即signature=sha1(string1)。 示例:

noncestr=Wm3WZYTPz0wzccnW

jsapi_ticket=sM4AOVdWfPE4DxkXGEs8VMCPGGVi4C3VM0P37wVUCFvkVAy_90u5h9nbSlYy3-Sl-HhTdfl2fzFy1AOcHKP7qg

timestamp=1414587457

    url=http://mp.weixin.qq.com?params=value

 

步骤1. 对所有待签名参数按照字段名的ASCII 码从小到大排序(字典序)后,使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串string1:

jsapi_ticket=sM4AOVdWfPE4DxkXGEs8VMCPGGVi4C3VM0P37wVUCFvkVAy_90u5h9nbSlYy3-Sl-HhTdfl2fzFy1AOcHKP7qg&noncestr=Wm3WZYTPz0wzccnW&timestamp=1414587457&url=http://mp.weixin.qq.com?params=value

步骤2. 对string1进行sha1签名,得到signature:

0f9de62fce790f9a083d5c99e95740ceb90c27ed

注意事项

  1. 签名用的noncestr和timestamp必须与wx.config中的nonceStr和timestamp相同。
  2. 签名用的url必须是调用JS接口页面的完整URL。
  3. 出于安全考虑,开发者必须在服务器端实现签名的逻辑。

如出现invalid signature 等错误详见附录5常见错误及解决办法,开启Debug,如签名无效会弹出invalid signature的提示

 

 

附录2-所有JS接口列表

复制代码
版本1.0.0接口

onMenuShareTimeline
onMenuShareAppMessage
onMenuShareQQ
onMenuShareWeibo
onMenuShareQZone
startRecord
stopRecord
onVoiceRecordEnd
playVoice
pauseVoice
stopVoice
onVoicePlayEnd
uploadVoice
downloadVoice
chooseImage
previewImage
uploadImage
downloadImage
translateVoice
getNetworkType
openLocation
getLocation
hideOptionMenu
showOptionMenu
hideMenuItems
showMenuItems
hideAllNonBaseMenuItem
showAllNonBaseMenuItem
closeWindow
scanQRCode
chooseWXPay
openProductSpecificView
addCard
chooseCard
openCard
复制代码

 

附录5-常见错误及解决方法

  

调用config 接口的时候传入参数 Debug: true 可以开启debug模式,页面会alert出错误信息。以下为常见错误及解决方法:

  1. invalid url domain当前页面所在域名与使用的appid没有绑定,请确认正确填写绑定的域名,如果使用了端口号,则配置的绑定域名也要加上端口号(一个appid可以绑定三个有效域名)
  2. invalid signature签名错误。建议按如下顺序检查:
    1. 确认签名算法正确,可用 http://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=jsapisign 页面工具进行校验。
    2. 确认config中nonceStr(js中驼峰标准大写S), timestamp与用以签名中的对应noncestr, timestamp一致。
    3. 确认url是页面完整的url(请在当前页面alert(location.href.split(‘#’)[0])确认),包括’http(s)://’部分,以及’?’后面的GET参数部分,但不包括’#’hash后面的部分。
    4. 确认 config 中的 appid 与用来获取 jsapi_ticket 的 appid 一致。
    5. 确保一定缓存access_token和jsapi_ticket。
    6. 确保你获取用来签名的url是动态获取的,动态页面可参见实例代码中php的实现方式。如果是html的静态页面在前端通过ajax将url传到后台签名,前端需要用js获取当前页面除去’#’hash部分的链接(可用location.href.split(‘#’)[0]获取,而且需要encodeURIComponent),因为页面一旦分享,微信客户端会在你的链接末尾加入其它参数,如果不是动态获取当前链接,将导致分享后的页面签名失败。
  3. the permission value is offline verifying这个错误是因为config没有正确执行,或者是调用的JSAPI没有传入config的jsApiList参数中。建议按如下顺序检查:
    1. 确认config正确通过。
    2. 如果是在页面加载好时就调用了JSAPI,则必须写在wx.ready的回调中。
    3. 确认config的jsApiList参数包含了这个JSAPI。
  4. permission denied该公众号没有权限使用这个JSAPI,或者是调用的JSAPI没有传入config的jsApiList参数中(部分接口需要认证之后才能使用)。
  5. function not exist当前客户端版本不支持该接口,请升级到新版体验。
  6. 为什么6.0.1版本config:ok,但是6.0.2版本之后不ok(因为6.0.2版本之前没有做权限验证,所以config都是ok,但这并不意味着你config中的签名是OK的,请在6.0.2检验是否生成正确的签名以保证config在高版本中也ok。)
  7. 在iOS和Android都无法分享(请确认公众号已经认证,只有认证的公众号才具有分享相关接口权限,如果确实已经认证,则要检查监听接口是否在wx.ready回调函数中触发)
  8. 服务上线之后无法获取jsapi_ticket,自己测试时没问题。(因为access_token和jsapi_ticket必须要在自己的服务器缓存,否则上线后会触发频率限制。请确保一定对token和ticket做缓存以减少2次服务器请求,不仅可以避免触发频率限制,还加快你们自己的服务速度。目前为了方便测试提供了1w的获取量,超过阀值后,服务将不再可用,请确保在服务上线前一定全局缓存access_token和jsapi_ticket,两者有效期均为7200秒,否则一旦上线触发频率限制,服务将不再可用)。
  9. uploadImage怎么传多图(目前只支持一次上传一张,多张图片需等前一张图片上传之后再调用该接口)
  10. 没法对本地选择的图片进行预览(chooseImage接口本身就支持预览,不需要额外支持)
  11. 通过a链接(例如先通过微信授权登录)跳转到b链接,invalid signature签名失败(后台生成签名的链接为使用jssdk的当前链接,也就是跳转后的b链接,请不要用微信登录的授权链接进行签名计算,后台签名的url一定是使用jssdk的当前页面的完整url除去’#’部分)
  12. 出现config:fail错误(这是由于传入的config参数不全导致,请确保传入正确的appId、timestamp、nonceStr、signature和需要使用的jsApiList)
  13. 如何把jsapi上传到微信的多媒体资源下载到自己的服务器(请参见文档中uploadVoice和uploadImage接口的备注说明)
  14. Android通过jssdk上传到微信服务器,第三方再从微信下载到自己的服务器,会出现杂音(微信团队已经修复此问题,目前后台已优化上线)
  15. 绑定父级域名,是否其子域名也是可用的(是的,合法的子域名在绑定父域名之后是完全支持的)
  16. 在iOS微信6.1版本中,分享的图片外链不显示,只能显示公众号页面内链的图片或者微信服务器的图片,已在6.2中修复
  17. 是否需要对低版本自己做兼容(jssdk都是兼容低版本的,不需要第三方自己额外做更多工作,但有的接口是6.0.2新引入的,只有新版才可调用)
  18. 该公众号支付签名无效,无法发起该笔交易(请确保你使用的jweixin.js是官方线上版本,不仅可以减少用户流量,还有可能对某些bug进行修复,拷贝到第三方服务器中使用,官方将不对其出现的任何问题提供保障,具体支付签名算法可参考 JSSDK微信支付一栏
  19. 目前Android微信客户端不支持pushState的H5新特性,所以使用pushState来实现web app的页面会导致签名失败,此问题已在Android6.2中修复
  20. uploadImage在chooseImage的回调中有时候Android会不执行,Android6.2会解决此问题,若需支持低版本可以把调用uploadImage放在setTimeout中延迟100ms解决
  21. require subscribe错误说明你没有订阅该测试号,该错误仅测试号会出现
  22. getLocation返回的坐标在openLocation有偏差,因为getLocation返回的是gps坐标,openLocation打开的腾讯地图为火星坐标,需要第三方自己做转换,6.2版本开始已经支持直接获取火星坐标
  23. 查看公众号(未添加): “menuItem:addContact”不显示,目前仅有从公众号传播出去的链接才能显示,来源必须是公众号
  24. ICP备案数据同步有一天延迟,所以请在第二日绑定

 

其他:

1.最好把微信的js以及对应的配置放在前面

2.关于图片不能正确获取的问题:

一般情况下,只要配置没问题,基本上就可以的,但是有时候,配置文件的图片明明给到了,但是那张分享图片仍不是我们想要的效果,而是可能直接抓取了body里面的第一张图片,所以

处理方法就是:在body里面最前面加入一张不影响页面的图片:

<div style=" overflow:hidden; width:0px; height:0; margin:0 auto; position:absolute; top:-800px;"><img src="http://pic4.nipic.com/20090907/1628220_101501018346_2.jpg"></div>

 

3.关于标题及描述获取不正确的问题:

虽然配置里面title,desc,都是完整的,但分享出去的结果还是错误的,所以最好在html的head里面也加上title,及描述

 

<title>微信二次分享</title>
<meta name="keywords" content="微信二次分享" />
<meta name="description" content="获取微信二次分享描述" />

4.分享小技巧:

获取“分享到朋友圈”按钮点击状态及自定义分享内容接口

复制代码
wx.onMenuShareTimeline({
    title: '', // 分享标题
    link: '', // 分享链接
    imgUrl: '', // 分享图标
    success: function () { 
        // 用户确认分享后执行的回调函数
    },
    cancel: function () { 
        // 用户取消分享后执行的回调函数
    }
});
复制代码

 

由于分享到朋友圈,是没有分享描述的,右边的文字取的是title的值,所以为了右边的文字能够更好地展示我们需要传达的信息,我们可以把title的值换成描述的值

 

5.建议使用微信web开发者工具进行调试,由于集成了Chrome DevTools,所以调试起来更加方便

http://mp.weixin.qq.com/wiki/10/e5f772f4521da17fa0d7304f68b97d7e.html

6.确保公众号已获取基础权限以及分享权限

官网文档《微信JS-SDK说明文档

由于博文不能保持和官网最新同步,所以可能会有出入

#JS-SDK

https://mp.weixin.qq.com/wiki?action=doc&id=mp1421141115&t=0.9227353854310705#buzhou2

以上就是文档部分,下篇将把代码部分放出来,仅供参考,不喜勿喷。下篇地址:关于微信二次分享,描述变链接的解决方法(二)—-代码部分

关于微信二次分享,描述变链接的解决方法(一)—-文档说明 – joshua317 – 博客园已关闭评论
2019年1月5日 By mikel 分类: JavaScript, 架构设计
http://baa.im/847971

UI组件 weui-wxss ★852 – 同微信原生视觉体验一致的基础样式库 Wa-UI ★122 – 针对微信小程序整合的一套UI库 wx-charts ★105 –

来源: 微信小程序UI组件、开发框架、实用库… – icyhoo – 博客园

UI组件

开发框架

实用库

开发工具

服务端

其他

Demo

微信小程序UI组件、开发框架、实用库… – icyhoo – 博客园已关闭评论
2019年1月5日 By mikel 分类: 架构设计
http://baa.im/847971

来源: 微信小程序开发框架搭建 – hss01248的专栏 – CSDN博客

使用开发工具的正确姿势
微信提供的开发工具的编辑功能不是一般的水,写代码肯定不能用它,否则就是浪费生命.不说别的,连自动保存都没有,第一次写时写了一个多小时,后面下班直接关掉,也不弹出提示说没保存.然后第二天过来,写的代码全没了!!! 顿时感到巨坑无比.这些工具开发人员吃干饭的么???
(后来的版本已经修复不能自动保存的问题了,当然编辑功能还是不好用.)

它的正确用法是作为运行和调试工具.

那么适合作为编辑工具的是: webStorm.基于IntelJ内核,开启Dracula主题,跟Android studio的使用习惯非常接近,so cool!各种方法提示,自动保存,快速查找…应有尽有.闭源的微信开发工具就不要用来写代码了,珍惜生命.
webStorm要识别wxml和wxss,还需要配置一下文件类型:(看下面别人截的图)
记住html和css里都要加上微信小程序对应的类型

综上,开发时,用webstorm来写代码,用微信开发工具来运行和调试,速度飕飕的!

网络请求的封装
微信提供了底层网络驱动以及成功和失败的回调.但对于一个项目中的实际使用而言,仍然还是显得繁琐,还有很多封装和简化的空间.

wx.request({
url: ‘test.php’,//请求的url
data: {//请求的参数
x: ” ,
y: ”
},
header: {//请求头
‘Content-Type’: ‘application/json’
},
method:”POST”,
success: function(res) {//成功的回调
console.log(res.data)
}
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
网络框架二次封装的一般姿势
对于一个网络访问来说,请求一般是get和post,拼上各种参数以及请求头,然后拿到回来的响应,解析并得到最终需要的数据.

对于具体项目来说,请求时会有每个(或大多数)请求都要带的参数,都要带的请求头,返回的数据格式可能都是一致的,那么基于此,对微信的网络请求api进行二次封装:

在我目前的项目中,

请求:
大多数请求是post,基本上每个请求都需要携带sessionId来与服务器验证登录状态,还有很多请求是基于分页的,需要带上pageSize和pageIndex.
再跟页面逻辑关联起来,请求可能是因为第一次进入页面,或者刷新,或者上拉加载更多.

响应:
大多数拿到的数据格式是标准json格式,如下

{
“code”:1,
“data”:xxx,//可能是String,也可能是JsonObject,或JsonArray,也可能是null,或undefined
“msg”:yyy//可能为空

}
1
2
3
4
5
6
通过请求的状态码code来判断这个请求是否真正成功.我们的项目中还有常见的是code=5:登录过期或未登录,code=2: 没有找到对应的内容 等等.

我们实际使用中需要的:
如果是大多数情况的请求时,只需要指定:

url的尾部
该请求的非一般的参数
该请求由什么动作引起的(第一次进入,刷新,加载更多)
对于响应,我们只需要:

成功时:拿到data里面的数据
失败时,拿到失败的信息(细分一下,包括可以直接显示给用户的和不能让用户看到的),以及失败状态码
数据为空的回调:(常见于列表数据的加载)
我们期望的api是:

netUtil.buildRequest(page,urlTail,params,callback)//必须的参数和回调
.setXxx(xxx)//额外的设置,链式调用
..
.send();//最终发出请求的动作
1
2
3
4
基于上面的分析,封装如下:
定义好携带构建请求的对象:
//这两个错误码是项目接口文档统一定义好的
const code_unlogin = 5;
const code_unfound = 2;

function requestConfig(){
this.page; //页面对象
this.isNewApi = true;
this.urlTail=”;
this.params={
pageIndex:0,
pageSize:getApp().globalData.defaultPageSize,
session_id:getApp().globalData.session_id
};
this.netMethod=’POST’;
this.callback={
onPre: function(){},
onEnd: function(){

},
onSuccess:function (data){},
onEmpty : function(){},
onError : function(msgCanShow,code,hiddenMsg){},
onUnlogin: function(){
this.onError(“您还没有登录或登录已过期,请登录”,5,”)
},
onUnFound: function(){
this.onError(“您要的内容没有找到”,2,”)
}
};

this.setMethodGet = function(){
this.netMethod = ‘GET’;
return this;
}

this.setApiOld = function(){
this.isNewApi = false;
return this;
}

this.send = function(){
request(this);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
请求的封装
供我们调用的顶层api:
//todo 拷贝这段代码去用–buildRequest里的callback
/*
onPre: function(){},
onEnd: function(){
hideLoadingDialog(page);
},
onSuccess:function (data){},
onEmpty : function(){},
onError : function(msgCanShow,code,hiddenMsg){},
onUnlogin: function(){
this.onError(“您还没有登录或登录已过期,请登录”,5,”)
},
onUnFound: function(){
this.onError(“您要的内容没有找到”,2,”)
}

* */

/**
* 注意,此方法调用后还要调用.send()才是发送出去.
* @param page
* @param urlTail
* @param params
* @param callback 拷贝上方注释区的代码使用
* @returns {requestConfig}
*/
function buildRequest(page,urlTail,params,callback){
var config = new requestConfig();
config.page = page;
config.urlTail = urlTail;

if (getApp().globalData.session_id == null || getApp().globalData.session_id == ”){
params.session_id=”
}else {
params.session_id = getApp().globalData.session_id;
}
if (params.pageIndex == undefined || params.pageIndex <=0 || params.pageSize == 0){
params.pageSize=0
}else {
if (params.pageSize == undefined){
params.pageSize = getApp().globalData.defaultPageSize;
}
}
log(params)
config.params = params;

log(config.params)

//config.callback = callback;

if(isFunction(callback.onPre)){
config.callback.onPre=callback.onPre;
}

if(isFunction(callback.onEnd)){
config.callback.onEnd=callback.onEnd;
}

if(isFunction(callback.onEmpty)){
config.callback.onEmpty=callback.onEmpty;
}

if(isFunction(callback.onSuccess)){
config.callback.onSuccess=callback.onSuccess;
}

if(isFunction(callback.onError)){
config.callback.onError=callback.onError;
}

if(isFunction(callback.onUnlogin)){
config.callback.onUnlogin=callback.onUnlogin;
}
if(isFunction(callback.onUnFound)){
config.callback.onUnFound=callback.onUnFound;
}
return config;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
最终请求的发送:
function request(requestConfig){

//检验三个公有参数并处理.这里与上面有所重复,是为了兼容之前写的几个api,不想改了.
requestConfig.params.sessionId= getApp().globalData.sessionId;
if (requestConfig.params.sessionId ==null || requestConfig.params.sessionId == ”){
delete requestConfig.params.sessionId;
}
if (requestConfig.params.pageIndex ==0 || requestConfig.params.pageSize == 0){
delete requestConfig.params.pageIndex ;
delete requestConfig.params.pageSize;
}

//var body = getStr(“&”, requestConfig.params);//拼接请求参数
requestConfig.onPre();//请求发出前
wx.request({
// url: getApp().globalData.apiHeadUrl+requestConfig.urlTail+”?”+body,貌似这样写,同时不给data赋值,post请求也是可以成功的
url: getApp().globalData.apiHeadUrl+requestConfig.urlTail,
method:requestConfig.netMethod,
data:requestConfig.params,
header: {‘Content-Type’:’application/json’},
success: function(res) {
console.log(res);
if(res.statusCode = 200){
var responseData = res.data
var code = responseData.code;
var msg = responseData.message;

if(code == 0){
var data = responseData.data;
var isDataNull = isOptStrNull(data);
if(isDataNull){
requestConfig.onEmpty();
}else{
requestConfig.onSuccess(data);
}
}else if(code == 2){
requestConfig.onUnFound();
}else if(code == 5){
requestConfig.onUnlogin();
}else{
var isMsgNull = isOptStrNull(msg);
if(isMsgNull){
var isCodeNull = isOptStrNull(code);
if (isCodeNull){
requestConfig.onError(“数据异常!,请核查”,code,”);
}else {
requestConfig.onError(“数据异常!,错误码为”+code,code,”);
}

}else{
requestConfig.onError(msg,code,”);
}
}
}else if(res.statusCode >= 500){
requestConfig.onError(“服务器异常!”,res.statusCode,”);
}else if(res.statusCode >= 400 && res.statusCode < 500){
requestConfig.onError(“没有找到内容”,res.statusCode,”);
}else{
requestConfig.onError(“网络请求异常!”,res.statusCode,”);
}
},
fail:function(res){
console.log(“fail”,res)
requestConfig.onError(“网络请求异常!”,res.statusCode,”);

},
complete:function(res){
// that.setData({hidden:true,toast:true});
}
})
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
将方法暴露,并在需要时引用:
方法写在netUtil.js下,在该js文件最下方暴露方法:

module.exports = {
buildRequest:buildRequest

}
1
2
3
4
实际引用:

var netUtil=require(“../../utils/netUtil.js”);
1
实际使用时:
小技巧: js无法像java一样定义好了接口,然后IDE自动生成代码.可以这样: 将callback的空方法写到netUtil的buildRequest方法上方的注释区,每次用时,点击方法名跳到那边去拷贝即可.

var params = {};
params.id = id;

netUtil.buildRequest(that,API.Album.DETAIL,params,{
onPre: function(){
netUtil.showLoadingDialog(that);
},
onEnd:function(){

},
onSuccess:function (data){
netUtil.showContent(that);
….
},
onEmpty : function(){

},
onError : function(msgCanShow,code,hiddenMsg){
netUtil.showErrorPage(that,msgCanShow);
},
onUnlogin: function(){
this.onError(“您还没有登录或登录已过期,请登录”,5,”)
},
onUnFound: function(){
this.onError(“您要的内容没有找到”,2,”)
}
}).send();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
},

页面状态管理
对于大多数网络请求后显示的页面,有这么几种页面状态:
第一次进入时,网络请求过程中:显示”加载中”的状态
加载的内容为空,显示”空白页面”
加载发生错误,显示”错误页面”,此页面一般有一个点击重试的按钮.该按钮一般的逻辑是:如果没有网络则点击后去打开网络设置,如果有网络,则重新发送网络请求.
加载成功,就显示内容页.
对于已经加载成功了,显示了内容页的”下拉拉刷新”:
页面上方会有”刷新中”的ui显示,这个微信已经原生集成,无需处理.
刷新成功,最好是弹出toast提示数据刷新成功
刷新失败,可以不提示,也可以提示,看具体选择.
对于一些分批加载的列表数据,一般还有上拉”加载更多”的功能:
参考微信文档中ui设计规范,上拉加载更多的ui提示应该放在页面最下部占一行,而不应该在页面中间显示一个大大的loading的效果.

scrollview拉到最底部,触发加载事件,显示”加载中”的ui
加载成功,直接就将数据添加到原list上,这时也看不到最底部那行ui,所以不用处理
加载失败,则在那一行显示”加载失败”的字样,同时提示用户”上拉重试”,或者在那一行放置一个按钮,点击按钮重试.
封装
通过上面的分析,可以确定大部分页面的通用状态管理逻辑,那么就可以设计通用的状态管理模板了.

ui的显示是通过Page里的data中的数据来控制的,并通过page.setData({xxx})来刷新的,原先每个页面都拷贝同样的js属性和wxml代码去实现封装,后来进行了封装,js属性用方法来封装,通过微信提供的template封装共同的wxml代码,通过import或include导入到wxml中(但是不知什么bug,template一直无法起作用).

控制ui显示与否的属性的封装
function netStateBean(){
//toast的是老api,工具升级后无需设置了
this.toastHidden=true,
this.toastMsg=”,

this.loadingHidden=false,
this.emptyHidden = true,
this.emptyMsg=’暂时没有内容,去别处逛逛吧’,

this.errorMsg=”,
this.errorHidden=true,

this.loadmoreMsg=’加载中…’,
this.loadmoreHidden=true,
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
页面js里的使用:
Page(
data: {
title:’名师’,//todo 设置标题栏
emptyMsg:’暂时没有内容,去别处逛逛吧’,//todo 空白页面的显示内容
netStateBean: new netUtil.netStateBean(),

},

)
1
2
3
4
5
6
7
8
9
wxml里:
模板

<template name=”pagestate” >
<view class =”empty_view” wx:if=”{{!emptyHidden}}” >
<view class=”center_wrapper” >
<view class=”center_child” >
<icon type=”info” size=”45″/>
<view class=”msg”> {{emptyMsg}}</view>
</view>
</view>
</view>

<view class =”error_view” wx:if=”{{!errorHidden}}” >
<view class=”center_wrapper”>
<view class=”center_child” >
<icon type=”warn” size=”45″ />
<view class=”msg”> {{errorMsg}}</view>
<button class = “retrybtn” type=”warn” loading=”{{btnLoading}}”
disabled=”{{btnDisabled}}” catchtap=”onRetry” hover-class=”other-button-hover”> 点击重试 </button>
</view>
</view>
</view>

</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
使用

<!–状态管理模板–>
<import src=”../../template/pagestate.wxml”/>
<view >
<template is=”pagestate” data=”{{…netStateBean}}”/>
</view>
1
2
3
4
5
js中提供API来控制页面状态:
module.exports = {
showContent:showContent,
showErrorPage:showErrorPage,
showEmptyPage:showEmptyPage,
loadMoreNoData:loadMoreNoData,
loadMoreStart:loadMoreStart,
loadMoreError:loadMoreError,
hideLoadingDialog:hideLoadingDialog,
showLoadingDialog:showLoadingDialog,
showSuccessToast:showSuccessToast,
dismissToast:dismissToast,
showFailToast:showFailToast,
….

}

//具体的实现就是,拿到page对象中的data.netStateBean,修改部分数值,然后刷新ui.
function showEmptyPage(that){
hideLoadingDialog(that);
var bean = that.data.netStateBean;
bean.emptyHidden = false;
bean.loadingHidden = true;
var empty = that.data.emptyMsg;
if (isOptStrNull(empty)){//如果那个页面没有自定义空白说明文字,就使用默认的.
empty = “没有内容,去别的页面逛逛吧”
}
bean.emptyMsg= empty;
bean.contentHidden=true;//隐藏content页面
bean.errorHidden = true;//隐藏error页面
//刷新UI
that.setData({
netStateBean: bean
});
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
最终的效果:

常用页面模板的封装
整个页面就是只有一个简单的listview或gridview,数据从网络拉取,带上拉刷新和下拉加载更多的功能
分析
对于这种简单的页面来说,分析各页面不同的地方:
1.上一个页面传入的参数:
2.网络请求的url
3.网络请求的部分参数(其中分批加载的每批大小和第几批这两个参数的key在整个项目中都是一样的,每批大小的value可能不一样)
4.response数据回来后,从哪个字段中取出列表对应的数据,可能会不一样
5.对列表数据datas的每一条,有些字段的数据需要处理,处理的方式会不一样.
6.UI中:item内容和样式每个页面会不一样

7.标题栏文字
8.列表数据为空时的说明文字
9.非第一进入时调用onShow(相当于安卓中的onResume)时,是否自动刷新页面数据

js-页面加载逻辑全部封装在方法中:
netUtil.requestSimpleList(that,pageIndex,action);
1
js–page里:需要设置的都加上了todo注释,高亮显示,便于直接填内容
var utils=require(“../../utils/util.js”);
var netUtil=require(“../../utils/netUtil.js”);
var viewBeans=require(“../../beans/viewBeans.js”);
var infoBeans=require(“../../beans/infoBeans.js”);
var API=require(“../../utils/API.js”);

const request_firstIn = 1;
const request_refresh = 2;
const request_loadmore = 3;
const request_none = 0;

var app = getApp();
var that;
var id =0; //页面id
var intentDatas;
var isFristIn = true;

var needRefreshOnResume = false;//todo 页面需要自己决定

Page({
data: {
title:’我的收藏’,//todo 设置标题栏
emptyMsg:’暂时没有内容,去别处逛逛吧’,//todo 空白显示内容
requestMethod:”POST”,//todo 如果不是post,则在这里改
urlTail:API.User.MYCOLLECTION,//todo 需补全的,页面请求的url

netStateBean: new netUtil.netStateBean(),
currentPageIndex:1,
currentAction : 0,
infos:[],//列表数据
},

//以下四个方法是生命周期方法,已封装好,无需改动
onLoad: function(options) {
that = this;
intentDatas = options;
if (that.data.emptyMsg != null && that.data.emptyMsg != ” ){
that.data.netStateBean.emptyMsg = that.data.emptyMsg;
}
that.parseIntent(options);
this.requestList(1,request_firstIn)
},
onReady: function () {
wx.setNavigationBarTitle({
title: this.data.title
});
},
onHide:function(){

},
onShow:function(){
if (isFristIn){
isFristIn = false;
}else {
if (needRefreshOnResume){
if (that.data.currentAction ==request_none){
this.requestList(1,request_refresh);//刷新
}
}
}
},
//上拉加载更多
onLoadMore: function(e) {
console.log(e);
console.log(that.data.currentAction +”—“+request_none);
if (that.data.currentAction ==request_none){
this.requestList(that.data.currentPageIndex+1,request_loadmore)
}
},
//下拉刷新,通过onPullDownRefresh来实现,这里不做动作
onRefesh: function(e) {
console.log(e);
},

onPullDownRefresh:function(e){
this.requestList(1,request_refresh);
},

//针对纯listview,网络请求直接一行代码调用
requestList:function(pageIndex, action){
netUtil.requestSimpleList(that,pageIndex,action)
},

//todo 滑动监听,各页面自己回调
scroll: function(e) {
console.log(e)
},
//todo 将intent传递过来的数据解析出来
parseIntent:function(options){

},

//todo 设置网络参数
/**
* 设置网络参数
* @param params config.params
* @param id 就是请求时传入的id,也是成员变量-id
*/
setNetparams: function (params) {

},

//todo 如果list数据是netData里一个字段,则更改此处
getListFromNetData:function(netData){
return netData;
},

//todo 数据的一些处理并刷新数据
handldItemInfo:function(info){

}
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
wxml中,写好空白页面,错误页面,加载更多栏
<view class=”section” style=”width: 100% ;height: 100%”>

<scroll-view wx:if=”{{!netStateBean.contentHidden}}” scroll-y=”true” style=”height:1300rpx;position:relative; z-index:0;” lower-threshold=”50″
bindscrolltolower=”onLoadMore” bindscrolltoupper=”onRefesh” >

<!–todo listview–>

<view class=”list_data”>
<block wx:for-items=”{{infos}}” wx:for-item=”info” wx:for-index=”index”>
<navigator url=”/pages/lession/{{info.classname}}?id={{info.id}}&title={{info.title}}” hover-class=””>
<!–todo—listview: 这里写item的具体内容 –>

</navigator>
</block>
</view>

<!–todo gridview 同时js中getListFromNetData()方法返回utils.to2DimensionArr(netData,columnNum);–>

<block wx:for-items=”{{infos}}” wx:for-item=”info” >
<view class=”row-container”>
<block wx:for-items=”{{info}}” wx:for-item=”item”>
<navigator url=”/pages/lession/album?id={{item.id}}” hover-class=””>
<!–todo gridview 这里写item具体的内容–>

</navigator>
</block>
</view>
</block>

<!–加载更多的条栏–>
<view class=”loadmore_view” wx:if=”{{!netStateBean.loadmoreHidden}}” >
{{netStateBean.loadmoreMsg}}
</view>
</scroll-view>

<!–空白页面–>
<view class =”empty_view” wx:if=”{{!netStateBean.emptyHidden}}” >
<view class=”center_wrapper” >
<view class=”center_child” >
<icon type=”info” size=”45″/>
<view class=”msg”> {{netStateBean.emptyMsg}}</view>
</view>
</view>
</view>

<!–错误页面–>
<view class =”error_view” wx:if=”{{!netStateBean.errorHidden}}” >
<view class=”center_wrapper”>
<view class=”center_child” >
<icon type=”warn” size=”45″ />
<view class=”msg”> {{netStateBean.errorMsg}}</view>
<button class = “retrybtn” type=”warn” loading=”{{loading}}”
disabled=”{{disabled}}” bindtap=”onRetry” hover-class=”other-button-hover”> 点击重试 </button>
</view>
</view>
</view>

</view>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
日志打印控制
function log(msg){
var isDebug = getApp().globalData.isDebug;
if (isDebug){
console.log(msg);
}
1
2
3
4
5
}

Android中的gridview在小程序里的实现
小程序文档中只提供了一维的列表渲染示例,相对应的就是安卓中的列表listview.一维的数组,一维的UI.
如果是实现gridview这种XY轴两个方向的延伸,数据方面需要一个二维数组,UI方法,需要两层的嵌套渲染.

数据转换:一维数组转换成二维数组
/**
*
* @param arr 原始数据,是一个一维数组
* @param num 变成二维数组后,每个小一维数组的元素个数,也就是gridview中每行的个数
*/
function to2DimensionArr(arr,num){
var newArr = new Array();//二维数组
if (arr == undefined){
return newArr;
}
var subArr=null;
for(var i =0;i<arr.length;i++){
var item = arr[i];
if((i%num==0) ||subArr==null){
subArr = new Array();//内部的一维数组
newArr.push(subArr);
}
subArr.push(item);
}
return newArr;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
UI上嵌套渲染:
<block wx:for-items=”{{infos}}” wx:for-item=”info” >
<view class=”row-container”>
<block wx:for-items=”{{info}}” wx:for-item=”albumItem”>
<view class=”row-album-item”>
<navigator url=”/pages/lession/album?id={{albumItem.id}}” hover-class=””>
<image mode=”aspectFill” src=”{{albumItem.coverUrl}}” class=”album-cover-img”/>
<text class=”album-name”>{{albumItem.name}}</text>
</navigator>
</view>
</block>
</view>
</block>
1
2
3
4
5
6
7
8
9
10
11
12
效果

代码
wxapp-devFrame
———————
作者:hss01248
来源:CSDN
原文:https://blog.csdn.net/hss01248/article/details/53405308
版权声明:本文为博主原创文章,转载请附上博文链接!

微信小程序开发框架搭建 – hss01248的专栏 – CSDN博客已关闭评论
备案信息冀ICP 0007948