最全的Windows Azure学习教程汇总
Windows Azure 是微软基于云计算的操作系统,能够为开发者提供一个平台,帮助开发可运行在云服务器、数据中心、Web 和 PC 上的应用程序。
Azure 是一种灵活和支持互操作的平台,能够将处于云端的开发者个人能力,同微软全球数据中心网络托管的服务,比如存储、计算和网络基础设施服务,紧密结合起来。帮助开发者在“云端”和“客户端”同时部署应用,使得企业与用户都能共享资源。
本文整理了丰富的 Windows Azure 学习资源,帮助开发者能全面地学习 Windows Azure 知识,并将 Windows Azure 运用在项目和实际工作中。
通过本系列博客,先来了解一下 Windows Azure 平台的基本知识。Windows Azure,正如同桌面操作系统 Windows 和服务器操作系统 Windows Server 一样,是一个云端的操作系统。开发人员可以使用同一套技术:.NET(包括 Silverlight),或者 Win32,同时针对桌面,服务器,以及云,开发程序,而不需要针对某个平台学习专门的技术。Visual Studio 和 Expression Studio 为开发人员提供了强大的工具支持。
Windows Azure平台简介(二):Windows Azure
Windows Azure平台简介(三):AppFabric
Windows Azure平台简介(四):SQL Azure以及其他服务
在开始本教学之前,请确保你从 Windows Azure 平台下载下载并安装了最新的 Windows Azure 开发工具。本教学使用 Visual Studio 2010 作为开发工具。
Windows Azure入门教学系列 (一):创建第一个WebRole程序
Windows Azure入门教学系列 (二):部署第一个Web Role程序
Windows Azure入门教学系列 (三):创建第一个Worker Role程序
Windows Azure入门教学系列 (四):使用Blob Storage
Windows Azure入门教学系列 (五):使用Queue Storage
Windows Azure入门教学系列 (六):使用Table Storage
Windows Azure入门教学系列 (七):使用REST API访问Storage Service
Windows Azure入门教学系列 (八):使用Windows Azure Drive
Azure Storage 是微软 Azure 云提供的云端存储解决方案,当前支持的存储类型有 Blob、Queue、File 和 Table。

Azure Blob Storage 基本用法 – Azure Storage 之 Blob
Azure Queue Storage 基本用法 – Azure Storage 之 Queue
Azure File Storage 基本用法 – Azure Storage 之 File
Azure Table storage 基本用法 – Azure Storage 之 Table
Windows Azure Storage 支持三重冗余的。保存在 Azure Storage 的内容,会在同一个数据中心保留有3个副本。这样的好处显而易见:当数据中心发生一般性故障的时候,比如磁盘损坏,机架服务器损坏等,用户保存在 Azure Storage 的数据不会丢失。每次对于 Storage 的写操作,都会对三个副本进行同步写操作,等到在副本操作完毕之后,才会返回执行成功给客户端。
Windows Azure 提供了三种不同类型的存储服务(这里的存储是非关系型数据,比如图片、文档等文件),用来提供给 Windows Azure 上运行的应用程序存储数据使用。依据不同的存储格式会有不同的限制,因为这些存储服务都是以分散式巨量存储(Distributed Mass Storage)为核心概念所设计出来的,为了要达成快速在分散式存储空间中存储与管理数据(还包含高可用度的赘余存储管理),微软有在数据的存储上做一些限制。
微软还提供了 REST API 来方便用户操作 Storage Service。
(1)Windows Azure Storage Service存储服务
(2)Windows Azure Storage Service存储服务之Blob详解(上)
(3)Windows Azure Storage Service存储服务之Blob详解(中)
(4)Windows Azure Storage Service存储服务之Blob Share Access Signature
(6)Windows Azure Storage之Table
(7)使用工具管理Windows Azure Storage
(8)Windows Azure 上的托管服务CDN (上)
(9)Windows Azure 上的托管服务CDN (中) Blob Service
(10)Windows Azure 上的托管服务CDN (下) Hosted Service、
(11)计算你存储的Blob的大小
(14)使用Azure Blob的PutBlock方法,实现文件的分块、离线上传
(15)使用WCF服务,将本地图片上传至Azure Storage (上) 服务器端代码
(16)使用WCF服务,将本地图片上传至Azure Storage (上) 客户端代码
(17)Azure Storage读取访问地域冗余(Read Access – Geo Redundant Storage, RA-GRS)
(18)使用HTML5 Portal的Azure CDN服务
(19)再谈Azure Block Blob和Page Blob
(21)使用AzCopy工具,加快Azure Storage传输速度
PowerShell 是管理 Azure 的最好方式之一,通过使用 PowerShell 脚本可以把很多的工作自动化。比如对于 Azure 上的虚拟机,可以设置定时关机操作,并在适当的时间把它开机,这样就能减少虚拟机的运行时间,同时也能为节能减排做出贡献。
(1)PowerShell入门
(2)修改Azure订阅名称
(3)上传证书
(5)使用Azure PowerShell创建简单的Azure虚拟机和Linux虚拟机
(6)设置单个Virtual Machine Endpoint
(7)使用CSV文件批量设置Virtual Machine Endpoint
(9)使用PowerShell导出订阅下所有的Azure VM的Public IP和Private IP
(10)使用PowerShell导出订阅下所有的Azure VM和Cloud Service的高可用情况
(11)使用自定义虚拟机镜像模板,创建Azure虚拟机并绑定公网IP(VIP)和内网IP(DIP)
(12)通过Azure PowerShell创建SSH登录的Linux VM
SQL Azure 是微软基于 Microsoft SQL Server Denali,也就是 SQL Server 2012 构建的云端关系型数据库服务。SQL Azure 是 SQL Server 的一个大子集,能够实现 SQL Server 的绝大部分功能,并且将它们作为云端的服务来扩展。SQL Azure Database 提供内置的高精准、可用性、功效与其他功能。
(1)入门
(5)使用SQL Server Management Studio连接SQL Azure
(6)使用Project Houston管理SQL Azure
(7)在SQL Azure Database中执行的T-SQL
(8)使用Visual Studio 2010开发应用连接SQL Azure云端数据库
(9)把本地的SQL Server数据库迁移到SQL Azure云数据库上
(10)SQL Azure Data Sync数据同步功能(上)
(11)SQL Azure Data Sync数据同步功能(下)
(12)使用新Portal 创建 SQL Azure Database
(13)Azure的两种关系型数据库服务:SQL Azure与SQL Server VM的不同
(14)将云端SQL Azure中的数据库备份到本地SQL Server
(15)SQL Azure 新的规格
(17)SQL Azure V12 - 跨数据中心标准地域复制(Standard Geo-Replication)
(20)使用SQL Server 2016 Upgrade Advisor
(21)将整张表都迁移到Azure Stretch Database里
(22)迁移部分数据到Azure Stretch Database
1. 《Windows Azure 实战》全面深入,完整覆盖 Windows Azure 所有关键技术和理论,详细讲解云计算开发流程、云服务架构(可用性、可靠性和高性能)、云设备整合、系统整合,以及云计算项目的管理。
注重实战,68个精心策划的针对特定实际应用场景的真实案例,详细呈现案例的设计思路和完整实现步骤。

2. 《Windows Azure 从入门到精通》介绍了如何构建和管理云端的可扩展应用,一次一个知识点,同时辅之以适当的练习,可帮助读者轻松掌握基本的编程技能,掌握 Windows Azure 云计算平台的核心服务和特性,是一本理想的入门教程。

3. 《云计算与Azure平台实战》解决了从本地转移到基于云的应用程序时,可能面临的各种问题;展示了如何将 ASP.NET 身份验证和角色管理用应用于 Azure Web 角色;揭示了迁移到 Windows Azure 时把计算服务卸载到一个或多个 WorkerWeb 角色的益处;讲解如何为共享 Azure 表选择最合适的 PartionKey 和 RowKey 值的组合;探讨了改善 Azure 表的可扩展性和性能的方法。

4. 《走进云计算:Windows Azure实战手记》介绍了你必须学会的微软云开发技术,介绍目前最火爆的云计算,深入剖析微软最新的云开发平台,涵盖 Windows Azure 环境、存储服务、SQL Azure 数据库与 App Fabric 服务平台 Step by Step 递进教学,初学者可按部就班地学习云应用的开发技术。

相关阅读:
Azure Blob Storage 基本用法 – Azure Storage 之 Blob
Azure Queue Storage 基本用法 – Azure Storage 之 Queue
分布式ID生成方法生成演变
一、需求缘起
几乎所有的业务系统,都有生成一个记录标识的需求,例如:
(1)消息标识:message-id
(2)订单标识:order-id
(3)帖子标识:tiezi-id
这个记录标识往往就是数据库中的唯一主键,数据库上会建立聚集索引(cluster index),即在物理存储上以这个字段排序。
这个记录标识上的查询,往往又有分页或者排序的业务需求,例如:
(1)拉取最新的一页消息:selectmessage-id/ order by time/ limit 100
(2)拉取最新的一页订单:selectorder-id/ order by time/ limit 100
(3)拉取最新的一页帖子:selecttiezi-id/ order by time/ limit 100
所以往往要有一个time字段,并且在time字段上建立普通索引(non-cluster index)。
我们都知道普通索引存储的是实际记录的指针,其访问效率会比聚集索引慢,如果记录标识在生成时能够基本按照时间有序,则可以省去这个time字段的索引查询:
select message-id/ (order by message-id)/limit 100
再次强调,能这么做的前提是,message-id的生成基本是趋势时间递增的。
这就引出了记录标识生成(也就是上文提到的三个XXX-id)的两大核心需求:
(1)全局唯一
(2)趋势有序
这也是本文要讨论的核心问题:如何高效生成趋势有序的全局唯一ID。
二、常见方法、不足与优化
【常见方法一:使用数据库的 auto_increment 来生成全局唯一递增ID】
优点:
(1)简单,使用数据库已有的功能
(2)能够保证唯一性
(3)能够保证递增性
(4)步长固定
缺点:
(1)可用性难以保证:数据库常见架构是一主多从+读写分离,生成自增ID是写请求,主库挂了就玩不转了
(2)扩展性差,性能有上限:因为写入是单点,数据库主库的写性能决定ID的生成性能上限,并且难以扩展
改进方法:
(1)增加主库,避免写入单点
(2)数据水平切分,保证各主库生成的ID不重复

如上图所述,由1个写库变成3个写库,每个写库设置不同的auto_increment初始值,以及相同的增长步长,以保证每个数据库生成的ID是不同的(上图中库0生成0,3,6,9…,库1生成1,4,7,10,库2生成2,5,8,11…)
改进后的架构保证了可用性,但缺点是:
(1)丧失了ID生成的“绝对递增性”:先访问库0生成0,3,再访问库1生成1,可能导致在非常短的时间内,ID生成不是绝对递增的(这个问题不大,我们的目标是趋势递增,不是绝对递增)
(2)数据库的写压力依然很大,每次生成ID都要访问数据库
为了解决上述两个问题,引出了第二个常见的方案
【常见方法二:单点批量ID生成服务】
分布式系统之所以难,很重要的原因之一是“没有一个全局时钟,难以保证绝对的时序”,要想保证绝对的时序,还是只能使用单点服务,用本地时钟保证“绝对时序”。数据库写压力大,是因为每次生成ID都访问了数据库,可以使用批量的方式降低数据库写压力。

如上图所述,数据库使用双master保证可用性,数据库中只存储当前ID的最大值,例如0。ID生成服务假设每次批量拉取6个ID,服务访问数据库,将当前ID的最大值修改为5,这样应用访问ID生成服务索要ID,ID生成服务不需要每次访问数据库,就能依次派发0,1,2,3,4,5这些ID了,当ID发完后,再将ID的最大值修改为11,就能再次派发6,7,8,9,10,11这些ID了,于是数据库的压力就降低到原来的1/6了。
优点:
(1)保证了ID生成的绝对递增有序
(2)大大的降低了数据库的压力,ID生成可以做到每秒生成几万几十万个
缺点:
(1)服务仍然是单点
(2)如果服务挂了,服务重启起来之后,继续生成ID可能会不连续,中间出现空洞(服务内存是保存着0,1,2,3,4,5,数据库中max-id是5,分配到3时,服务重启了,下次会从6开始分配,4和5就成了空洞,不过这个问题也不大)
(3)虽然每秒可以生成几万几十万个ID,但毕竟还是有性能上限,无法进行水平扩展
改进方法:
单点服务的常用高可用优化方案是“备用服务”,也叫“影子服务”,所以我们能用以下方法优化上述缺点(1):
如上图,对外提供的服务是主服务,有一个影子服务时刻处于备用状态,当主服务挂了的时候影子服务顶上。这个切换的过程对调用方是透明的,可以自动完成,常用的技术是vip+keepalived,具体就不在这里展开。

【常见方法三:uuid】
上述方案来生成ID,虽然性能大增,但由于是单点系统,总还是存在性能上限的。同时,上述两种方案,不管是数据库还是服务来生成ID,业务方Application都需要进行一次远程调用,比较耗时。有没有一种本地生成ID的方法,即高性能,又时延低呢?
uuid是一种常见的方案:string ID =GenUUID();
优点:
(1)本地生成ID,不需要进行远程调用,时延低
(2)扩展性好,基本可以认为没有性能上限
缺点:
(1)无法保证趋势递增
(2)uuid过长,往往用字符串表示,作为主键建立索引查询效率低,常见优化方案为“转化为两个uint64整数存储”或者“折半存储”(折半后不能保证唯一性)
【常见方法四:取当前毫秒数】
uuid是一个本地算法,生成性能高,但无法保证趋势递增,且作为字符串ID检索效率低,有没有一种能保证递增的本地算法呢?
取当前毫秒数是一种常见方案:uint64 ID = GenTimeMS();
优点:
(1)本地生成ID,不需要进行远程调用,时延低
(2)生成的ID趋势递增
(3)生成的ID是整数,建立索引后查询效率高
缺点:
(1)如果并发量超过1000,会生成重复的ID
我去,这个缺点要了命了,不能保证ID的唯一性。当然,使用微秒可以降低冲突概率,但每秒最多只能生成1000000个ID,再多的话就一定会冲突了,所以使用微秒并不从根本上解决问题。
【常见方法五:类snowflake算法】
snowflake是twitter开源的分布式ID生成算法,其核心思想是:一个long型的ID,使用其中41bit作为毫秒数,10bit作为机器编号,12bit作为毫秒内序列号。这个算法单机每秒内理论上最多可以生成1000*(2^12),也就是400W的ID,完全能满足业务的需求。
借鉴snowflake的思想,结合各公司的业务逻辑和并发量,可以实现自己的分布式ID生成算法。
举例,假设某公司ID生成器服务的需求如下:
(1)单机高峰并发量小于1W,预计未来5年单机高峰并发量小于10W
(2)有2个机房,预计未来5年机房数量小于4个
(3)每个机房机器数小于100台
(4)目前有5个业务线有ID生成需求,预计未来业务线数量小于10个
(5)…
分析过程如下:
(1)高位取从2016年1月1日到现在的毫秒数(假设系统ID生成器服务在这个时间之后上线),假设系统至少运行10年,那至少需要10年*365天*24小时*3600秒*1000毫秒=320*10^9,差不多预留39bit给毫秒数
(2)每秒的单机高峰并发量小于10W,即平均每毫秒的单机高峰并发量小于100,差不多预留7bit给每毫秒内序列号
(3)5年内机房数小于4个,预留2bit给机房标识
(4)每个机房小于100台机器,预留7bit给每个机房内的服务器标识
(5)业务线小于10个,预留4bit给业务线标识

这样设计的64bit标识,可以保证:
(1)每个业务线、每个机房、每个机器生成的ID都是不同的
(2)同一个机器,每个毫秒内生成的ID都是不同的
(3)同一个机器,同一个毫秒内,以序列号区区分保证生成的ID是不同的
(4)将毫秒数放在最高位,保证生成的ID是趋势递增的
缺点:
(1)由于“没有一个全局时钟”,每台服务器分配的ID是绝对递增的,但从全局看,生成的ID只是趋势递增的(有些服务器的时间早,有些服务器的时间晚)
最后一个容易忽略的问题:
生成的ID,例如message-id/ order-id/ tiezi-id,在数据量大时往往需要分库分表,这些ID经常作为取模分库分表的依据,为了分库分表后数据均匀,ID生成往往有“取模随机性”的需求,所以我们通常把每秒内的序列号放在ID的最末位,保证生成的ID是随机的。
又如果,我们在跨毫秒时,序列号总是归0,会使得序列号为0的ID比较多,导致生成的ID取模后不均匀。解决方法是,序列号不是每次都归0,而是归一个0到9的随机数,这个地方。
下面附上C#.Net 实现snowflake算法实现
1 | using System; |
Shiro
Shiro
- 登陆、授权、拦截
- 按钮权限控制
一、目标
- Maven+Spring+shiro
- 自定义登陆、授权
- 自定义拦截器
- 加载数据库资源构建拦截链
使用总结:
1、需要设计的数据库:用户、角色、权限、资源
2、可以通过,角色,权限,两个拦截器同时确定是否能访问
3、角色与权限的关系,role1=permission1,permission2,多级的权限:sys:permission1,拥有高级权限同时用于低级权限。
4、perms[“permission1”] 为权限
5、拦截器机制介绍了拦截角色还是权限
6、角色与权限 是两个概念
7、权限-资源,一对一。资源分为上下级,因此权限分为父权限,子权限。创建资源的时候,创建权限。权限里资源的别名
8、角色-权限,一对多。角色里权限的别名
9、按钮是通过权限来控制的
10、防止有父级资源可以访问,子级资源不能访问的情况,不适用 sys:add 权限写法
二、代码
1、Pom.xml

1 <properties>
2 <spring.version>4.3.4.RELEASE</spring.version>
3 </properties>
4 <dependency>
5 <groupId>junit</groupId>
6 <artifactId>junit</artifactId>
7 <version>4.9</version>
8 </dependency>
9 <dependency>
10 <groupId>commons-logging</groupId>
11 <artifactId>commons-logging</artifactId>
12 <version>1.1.3</version>
13 </dependency>
14 <dependency>
15 <groupId>org.apache.shiro</groupId>
16 <artifactId>shiro-core</artifactId>
17 <version>1.2.2</version>
18 </dependency>
19 <dependency>
20 <groupId>org.apache.shiro</groupId>
21 <artifactId>shiro-spring</artifactId>
22 <version>1.2.2</version>
23 </dependency>
24 <dependency>
25 <groupId>javax.servlet</groupId>
26 <artifactId>javax.servlet-api</artifactId>
27 <version>3.0.1</version>
28 <scope>provided</scope>
29 </dependency>
30 <dependency>
31 <groupId>org.springframework</groupId>
32 <artifactId>spring-web</artifactId>
33 <version>${spring.version}</version>
34 </dependency>
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54

2、web.xml
Servlet拦截访问,使用注解更方便,需要删除项目中的servlet使用javax.servlet-api 3.0 包

1 package com.cyd.shiro;
2
3 import java.io.IOException;
4
5 import javax.servlet.ServletException;
6 import javax.servlet.annotation.WebServlet;
7 import javax.servlet.http.HttpServlet;
8 import javax.servlet.http.HttpServletRequest;
9 import javax.servlet.http.HttpServletResponse;
10
11 import org.apache.shiro.SecurityUtils;
12 import org.apache.shiro.authc.AuthenticationException;
13 import org.apache.shiro.authc.IncorrectCredentialsException;
14 import org.apache.shiro.authc.UnknownAccountException;
15 import org.apache.shiro.authc.UsernamePasswordToken;
16 import org.apache.shiro.subject.Subject;
17 import org.apache.shiro.web.util.SavedRequest;
18 import org.apache.shiro.web.util.WebUtils;
19 import org.junit.Test;
20
21 @WebServlet(name = “loginServlet”, urlPatterns = “/loginController”)
22 public class LoginServlet extends HttpServlet {
23 @Override
24 protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
25 req.getRequestDispatcher(“login.jsp”).forward(req, resp);
26 }
27
28 @Override
29 protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
30 System.out.println(LoginServlet.class.toString());
31 String error = null;
32 String username = req.getParameter(“username”);
33 String password = req.getParameter(“password”);
34 Subject subject = SecurityUtils.getSubject();
35 UsernamePasswordToken token = new UsernamePasswordToken(username, password);
36 try {
37 subject.login(token);
38 } catch (UnknownAccountException e) {
39 error = “用户名/密码错误”;
40 } catch (IncorrectCredentialsException e) {
41 error = “用户名/密码错误”;
42 } catch (AuthenticationException e) {
43 // 其他错误,比如锁定,如果想单独处理请单独catch处理
44 error = “其他错误:” + e.getMessage();
45 }
46 if (error != null) {// 出错了,返回登录页面
47 req.setAttribute(“error”, error);
48 req.getRequestDispatcher(“login.jsp”).forward(req, resp);
49 } else {// 登录成功
50 //跳转到拦截登陆前的地址
51 SavedRequest request=WebUtils.getSavedRequest(req);
52 String url =request.getRequestURI();
53 req.getRequestDispatcher(url.substring(url.lastIndexOf(‘/‘))).forward(req, resp);
54 }
55 }
56
57 }

3、Spring-shiro.xml

<beans xmlns=“http://www.springframework.org/schema/beans“ xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance“ xmlns:context=“http://www.springframework.org/schema/context“ xmlns:util=“http://www.springframework.org/schema/util“ xsi:schemaLocation=“http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util-4.2.xsd"\>
<context:component-scan base-package\="com.cyd.shiro.\*"\></context:component-scan\>
<!-- Shiro的Web过滤器 \-->
<bean id\="shiroFilter" class\="com.cyd.shiro.ExtendShiroFilterFactoryBean"\>
<property name\="securityManager" ref\="securityManager" />
<property name\="loginUrl" value\="/login.jsp" />
<!-- <property name="successUrl" value="/index.jsp" /> \-->
<property name\="unauthorizedUrl" value\="/unauthorized.jsp" />
<property name\="filters"\>
<util:map\>
<!-- <entry key="onperms" value-ref="URLPermissionsFilter" /> \-->
<entry key\="onrole" value-ref\="ExtendRolesAuthorizationFilter" />
</util:map\>
</property\>
<property name\="filterChainDefinitions"\>
<value\> /unauthorized.jsp = anon
/logoutController=anon
/login.jsp=authc
</value\>
</property\>
</bean\>
<!-- 安全管理器 \-->
<bean id\="securityManager" class\="org.apache.shiro.web.mgt.DefaultWebSecurityManager"\>
<property name\="realm" ref\="myRealm" />
<property name\="cacheManager" ref\="cacheManager" />
</bean\>
<!-- 自定义认证,授权 \-->
<bean id\="myRealm" class\="com.cyd.shiro.AdminRealm"\></bean\>
<!-- 注册ehcache,不然每次访问都要登陆 \-->
<bean id\="cacheManager" class\="org.apache.shiro.cache.ehcache.EhCacheManager"\>
<property name\="cacheManagerConfigFile" value\="classpath:ehcache.xml" />
</bean\>
<!-- 自定义鉴权拦截器 \-->
<bean id\="URLPermissionsFilter" class\="com.cyd.shiro.URLPermissionsFilter" />
<bean id\="ExtendRolesAuthorizationFilter" class\="com.cyd.shiro.ExtendRolesAuthorizationFilter" />
</beans>

4、Ehcache.xml 缓存

<ehcache xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance“ xsi:noNamespaceSchemaLocation=“../config/ehcache.xsd”>
<diskStore path=“java.io.tmpdir”/>
<defaultCache
maxElementsInMemory=“10000” eternal=“false” timeToIdleSeconds=“600” timeToLiveSeconds=“600” overflowToDisk=“true” maxElementsOnDisk=“10000000” diskPersistent=“false” diskExpiryThreadIntervalSeconds=“120” memoryStoreEvictionPolicy=“LRU”
/>
</ehcache>

5、登陆Servlet

package com.cyd.shiro;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.util.SavedRequest;
import org.apache.shiro.web.util.WebUtils;
@WebServlet(name = “loginServlet”, urlPatterns = “/loginController”)
public class LoginServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.getRequestDispatcher(“login.jsp”).forward(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println(LoginServlet.class.toString());
String error = null;
String username = req.getParameter("username");
String password = req.getParameter("password");
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
try {
subject.login(token);
} catch (UnknownAccountException e) {
error = "用户名/密码错误";
} catch (IncorrectCredentialsException e) {
error = "用户名/密码错误";
} catch (AuthenticationException e) {
// 其他错误,比如锁定,如果想单独处理请单独catch处理
error = "其他错误:" + e.getMessage();
}
if (error != null) {// 出错了,返回登录页面
req.setAttribute("error", error);
req.getRequestDispatcher("login.jsp").forward(req, resp);
} else {// 登录成功
//跳转到拦截登陆前的地址
SavedRequest request=WebUtils.getSavedRequest(req);
String url =request.getRequestURI();
req.getRequestDispatcher(url.substring(url.lastIndexOf('/'))).forward(req, resp);
}
}
}

6、自定义登陆、授权。
根据需求自定义登陆异常。从数据库查询出当前用户拥有的权限并授权

1 package com.cyd.shiro;
2
3 import java.util.HashSet;
4 import java.util.LinkedList;
5 import java.util.List;
6 import java.util.Set;
7
8 import org.apache.shiro.authc.AuthenticationException;
9 import org.apache.shiro.authc.AuthenticationInfo;
10 import org.apache.shiro.authc.AuthenticationToken;
11 import org.apache.shiro.authc.SimpleAuthenticationInfo;
12 import org.apache.shiro.authc.UnknownAccountException;
13 import org.apache.shiro.authz.AuthorizationInfo;
14 import org.apache.shiro.authz.SimpleAuthorizationInfo;
15 import org.apache.shiro.realm.AuthorizingRealm;
16 import org.apache.shiro.subject.PrincipalCollection;
17 import org.springframework.beans.factory.annotation.Autowired;
18
19 import com.cyd.helloworld.SysRoles;
20 import com.cyd.helloworld.SysUsers;
21 import com.cyd.shiro.admin.SysUsersService;
22
23 public class AdminRealm extends AuthorizingRealm {
24
25 @Autowired
26 private SysUsersService sysusersservice;
27 // 认证登陆
28 @Override
29 protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
30 System.out.println(“do doGetAuthenticationInfo”);
31 String username = (String) token.getPrincipal();
32 SysUsers user = sysusersservice.getSysUsers(username);
33 if (user == null) {
34 throw new UnknownAccountException();// 没找到帐号
35 }
36 SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user.getUserName(), // 用户名
37 user.getPassWorld(), // 密码
38 getName() // realm name
39 );
40 return authenticationInfo;
41 }
42
43 // 用户授权
44 @Override
45 protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
46 System.out.println(“do doGetAuthorizationInfo”);
47 String username = (String)principals.getPrimaryPrincipal();
48 SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
49 //从数据库加载当前用户的角色,例如:[admin]
50 authorizationInfo.setRoles(new HashSet
51 //从数据库加载当前用户可以访问的资源,例如:[index.jsp, abc.jsp]
52 authorizationInfo.setStringPermissions(new HashSet
53
54 return authorizationInfo;
55 }
56 }

7、自定义拦截器。
重写拦截器是因为shiro 验证是否有权限访问是需要当前用户拥有拦截器链的所有权限。一般需求只需要拥有部分权限即可。
角色验证拦截,hasRole和hasAllRoles 验证是否有权限。

1 package com.cyd.shiro;
2
3 import java.io.IOException;
4 import java.util.Set;
5
6 import javax.servlet.ServletRequest;
7 import javax.servlet.ServletResponse;
8
9 import org.apache.shiro.subject.Subject;
10 import org.apache.shiro.util.CollectionUtils;
11 import org.apache.shiro.web.filter.authz.RolesAuthorizationFilter;
12
13 /**
14 * 通过角色验证权限
15 * @author chenyd
16 * 2017年11月21日
17 */
18 public class ExtendRolesAuthorizationFilter extends RolesAuthorizationFilter{
19
20 @Override
21 public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws IOException {
22
23 System.out.println(ExtendRolesAuthorizationFilter.class.toString());
24 Subject subject = getSubject(request, response);
25 String[] rolesArray = (String[]) mappedValue;
26
27 if (rolesArray == null || rolesArray.length == 0) {
28 //no roles specified, so nothing to check - allow access.
29 return true;
30 }
31 //AbstractFilter
32 Set
33
34 boolean flag=false;
35 for(String role: roles){
36 if(subject.hasRole(role)){
37 flag=true;
38 break;
39 }
40 }
41 return flag;
42 }
43 }

url拦截校验,isPermitted和isPermittedAll验证是否有权限访问,

1 package com.cyd.shiro;
2
3 import java.io.IOException;
4
5 import javax.servlet.ServletRequest;
6 import javax.servlet.ServletResponse;
7 import javax.servlet.http.HttpServletRequest;
8
9 import org.apache.shiro.subject.Subject;
10 import org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter;
11 /**
12 * 通过字符串验证权限
13 * @author chenyd
14 * 2017年11月21日
15 */
16 public class URLPermissionsFilter extends PermissionsAuthorizationFilter {
17
18 /**
19 * mappedValue 访问该url时需要的权限
20 * subject.isPermitted 判断访问的用户是否拥有mappedValue权限
21 * 重写拦截器,只要符合配置的一个权限,即可通过
22 */
23 @Override
24 public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue)
25 throws IOException {
26 System.out.println(URLPermissionsFilter.class.toString());
27 Subject subject = getSubject(request, response);
28 // DefaultFilterChainManager
29 // PathMatchingFilterChainResolver
30 String[] perms = (String[]) mappedValue;
31 boolean isPermitted = false;
32 if (perms != null && perms.length > 0) {
33 for (String str : perms) {
34 if (subject.isPermitted(str)) {
35 isPermitted = true;
36 }
37 }
38 }
39
40 return isPermitted;
41 }
42 }

8、加载数据库资源构建拦截器链

1 package com.cyd.shiro;
2
3 import java.util.Map;
4
5 import org.apache.shiro.config.Ini;
6 import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
7 import org.apache.shiro.util.CollectionUtils;
8 import org.apache.shiro.web.config.IniFilterChainResolverFactory;
9 import org.springframework.beans.factory.annotation.Autowired;
10
11 import com.cyd.shiro.admin.SysUsersService;
12
13 public class ExtendShiroFilterFactoryBean extends ShiroFilterFactoryBean{
14
15 @Autowired
16 private SysUsersService sysusersservice;
17 //PathMatchingFilter
18 @Override
19 public void setFilterChainDefinitions(String definitions) {
20 //数据库中获取权限,{/index.jsp=authc,onrole[“admin2”,”admin”], /abc.jsp=authc,onrole[“admin2”,”admin”]}
21 Map<String, String> otherChains = sysusersservice.getFilterChain();
22 Ini ini = new Ini();
23 ini.load(definitions);
24 Ini.Section section = ini.getSection(IniFilterChainResolverFactory.URLS);
25 if (CollectionUtils.isEmpty(section)) {
26 section = ini.getSection(Ini.DEFAULT_SECTION_NAME);
27 }
28 section.putAll(otherChains);
29 setFilterChainDefinitionMap(section);
30 }
31
32 }

三、 学习笔记
1、INI文件配置

[users] #提供了对用户/密码及其角色的配置,用户名=密码,角色1,角色2
zhang=123,admin
[roles] #提供了角色及权限之间关系的配置,角色=权限1,权限2
admin=index.jsp
[urls] #配置拦截器链,/** 为拦截器链名称(filterChain),authc,roles[admin],perms[“index.jsp”]拦截器列表名
/login.jsp=anon
/loginController=anon
/unauthorized.jsp=anon
/**=authc,roles[admin],perms[“index.jsp”]

2、拦截器链
Shiro的所有拦截器链名定义在源码DefaultFilter中。
anon
例子/admins/**=anon 没有参数,表示可以匿名使用。
authc
例如/admins/user/**=authc表示需要认证(登录)才能使用,没有参数
roles
例子/admins/user/**=roles[admin],参数可以写多个,多个时必须加上引号,
并且参数之间用逗号分割,当有多个参数时,例如admins/user/**=roles[“admin,guest”],
每个参数通过才算通过,相当于hasAllRoles()方法。
perms
例子/admins/user/**=perms[user:add:*],参数可以写多个,多个时必须加上引号,并且参数之间用逗号分割,
例如/admins/user/**=perms[“user:add:*,user:modify:*“],当有多个参数时必须每个参数都通过才通过,
想当于isPermitedAll()方法。
rest
例子/admins/user/**=rest[user],根据请求的方法,相当于/admins/user/**=perms[user:method] ,
其中method为post,get,delete等。
port
例子/admins/user/**=port[8081],当请求的url的端口不是8081是跳转到schemal://serverName:8081?queryString, 其中schmal是协议http或https等,serverName是你访问的host,8081是url配置里port的端口,queryString是你访问的url里的?后面的参数。
authcBasic
例如/admins/user/**=authcBasic没有参数表示httpBasic认证
ssl
例子/admins/user/**=ssl没有参数,表示安全的url请求,协议为https
user
例如/admins/user/**=user没有参数表示必须存在用户,当登入操作时不做检查
注:anon,authcBasic,auchc,user是认证过滤器,
perms,roles,ssl,rest,port是授权过滤器
3、拦截器链源码类关系图
① NameableFilter有一个name属性,定义每一个filter的名字。
② OncePerRequestFilter保证客户端请求后该filter的doFilter只会执行一次。
doFilterInternal非常重要,在shiro整个filter体系中的核心方法及实质入口。另外,shiro是通过在request中设置一个该filter特定的属性值来保证该filter只会执行一次的。
③ AdviceFilter中主要是对doFilterInternal做了更细致的切分。
springmvc中的Interceptor,doFilterInternal会先调用preHandle做一些前置判断,如果返回false则filter链不继续往下执行,
④ AccessControlFilter中的对onPreHandle方法做了进一步细化。
isAccessAllowed方法和onAccessDenied方法达到控制效果。这两个方法都是抽象方法,由子类去实现。到这一层应该明白。isAccessAllowed和onAccessDenied方法会影响到onPreHandle方法,而onPreHandle方法会影响到preHandle方法,而preHandle方法会达到控制filter链是否执行下去的效果。所以如果正在执行的filter中isAccessAllowed和onAccessDenied都返回false,则整个filter控制链都将结束,不会到达目标方法(客户端请求的接口),而是直接跳转到某个页面(由filter定义的,将会在authc中看到)。
⑤ FormAuthenticationFiltershiro提供的登录的filter,
saveRequestAndRedirectToLogin保存request并拦截到登陆页面,登陆成功后可从WebUtils.getSavedRequest(req);中取出。
四、未实现的功能
- 动态URL权限控制。当修改权限时,重新加载拦截器链。
- 密码加密
- 记住我
- 在线人数控制
- 集成验证码
五、参考链接
- spring mvc整合shiro登录 权限验证 http://blog.csdn.net/rongku/article/details/51336424
- Shiro(4)默认鉴权与自定义鉴权 http://blog.csdn.net/zhengwei223/article/details/9981741
- 拦截器机制-跟我学shiro http://jinnianshilongnian.iteye.com/blog/2025656
- shiro Filter–拦截器源码解释 https://www.cnblogs.com/yoohot/p/6085830.html
- 动态URL http://blog.csdn.net/shadowsick/article/details/39001273
- 重写shirofilterbean方式加载数据库资源权限 http://blog.csdn.net/qq_18333833/article/details/70243620
Mybatis Generator最完整配置详解
同学们有福了,花了一些时间,重新整理了一个最完整的Mybatis Generator(简称MBG)的最完整配置文件,带详解,再也不用去看EN的User Guide了;

<generatorConfiguration>
<context id=“mysql” defaultModelType=“hierarchical” targetRuntime=“MyBatis3Simple” >
<!-- 自动识别数据库关键字,默认false,如果设置为true,根据SqlReservedWords中定义的关键字列表;
一般保留默认值,遇到数据库关键字(Java关键字),使用columnOverride覆盖 \-->
<property name\="autoDelimitKeywords" value\="false"/>
<!-- 生成的Java文件的编码 \-->
<property name\="javaFileEncoding" value\="UTF-8"/>
<!-- 格式化java代码 \-->
<property name\="javaFormatter" value\="org.mybatis.generator.api.dom.DefaultJavaFormatter"/>
<!-- 格式化XML代码 \-->
<property name\="xmlFormatter" value\="org.mybatis.generator.api.dom.DefaultXmlFormatter"/>
<!-- beginningDelimiter和endingDelimiter:指明数据库的用于标记数据库对象名的符号,比如ORACLE就是双引号,MYSQL默认是\`反引号; \-->
<property name\="beginningDelimiter" value\="\`"/>
<property name\="endingDelimiter" value\="\`"/>
<!-- 必须要有的,使用这个配置链接数据库
@TODO:是否可以扩展 \-->
<jdbcConnection driverClass\="com.mysql.jdbc.Driver" connectionURL\="jdbc:mysql:///pss" userId\="root" password\="admin"\>
<!-- 这里面可以设置property属性,每一个property属性都设置到配置的Driver上 \-->
</jdbcConnection\>
<!-- java类型处理器
用于处理DB中的类型到Java中的类型,默认使用JavaTypeResolverDefaultImpl;
注意一点,默认会先尝试使用Integer,Long,Short等来对应DECIMAL和 NUMERIC数据类型; \-->
<javaTypeResolver type\="org.mybatis.generator.internal.types.JavaTypeResolverDefaultImpl"\>
<!-- true:使用BigDecimal对应DECIMAL和 NUMERIC数据类型
false:默认,
scale>0;length>18:使用BigDecimal;
scale=0;length\[10,18\]:使用Long;
scale=0;length\[5,9\]:使用Integer;
scale=0;length<5:使用Short; \-->
<property name\="forceBigDecimals" value\="false"/>
</javaTypeResolver\>
<!-- java模型创建器,是必须要的元素
负责:1,key类(见context的defaultModelType);2,java类;3,查询类
targetPackage:生成的类要放的包,真实的包受enableSubPackages属性控制;
targetProject:目标项目,指定一个存在的目录下,生成的内容会放到指定目录中,如果目录不存在,MBG不会自动建目录 \-->
<javaModelGenerator targetPackage\="com.\_520it.mybatis.domain" targetProject\="src/main/java"\>
<!-- for MyBatis3/MyBatis3Simple
自动为每一个生成的类创建一个构造方法,构造方法包含了所有的field;而不是使用setter; \-->
<property name\="constructorBased" value\="false"/>
<!-- 在targetPackage的基础上,根据数据库的schema再生成一层package,最终生成的类放在这个package下,默认为false \-->
<property name\="enableSubPackages" value\="true"/>
<!-- for MyBatis3 / MyBatis3Simple
是否创建一个不可变的类,如果为true,
那么MBG会创建一个没有setter方法的类,取而代之的是类似constructorBased的类 \-->
<property name\="immutable" value\="false"/>
<!-- 设置一个根对象,
如果设置了这个根对象,那么生成的keyClass或者recordClass会继承这个类;在Table的rootClass属性中可以覆盖该选项
注意:如果在key class或者record class中有root class相同的属性,MBG就不会重新生成这些属性了,包括:
1,属性名相同,类型相同,有相同的getter/setter方法; \-->
<property name\="rootClass" value\="com.\_520it.mybatis.domain.BaseDomain"/>
<!-- 设置是否在getter方法中,对String类型字段调用trim()方法 \-->
<property name\="trimStrings" value\="true"/>
</javaModelGenerator\>
<!-- 生成SQL map的XML文件生成器,
注意,在Mybatis3之后,我们可以使用mapper.xml文件+Mapper接口(或者不用mapper接口),
或者只使用Mapper接口+Annotation,所以,如果 javaClientGenerator配置中配置了需要生成XML的话,这个元素就必须配置
targetPackage/targetProject:同javaModelGenerator \-->
<sqlMapGenerator targetPackage\="com.\_520it.mybatis.mapper" targetProject\="src/main/resources"\>
<!-- 在targetPackage的基础上,根据数据库的schema再生成一层package,最终生成的类放在这个package下,默认为false \-->
<property name\="enableSubPackages" value\="true"/>
</sqlMapGenerator\>
<!-- 对于mybatis来说,即生成Mapper接口,注意,如果没有配置该元素,那么默认不会生成Mapper接口
targetPackage/targetProject:同javaModelGenerator
type:选择怎么生成mapper接口(在MyBatis3/MyBatis3Simple下):
1,ANNOTATEDMAPPER:会生成使用Mapper接口+Annotation的方式创建(SQL生成在annotation中),不会生成对应的XML;
2,MIXEDMAPPER:使用混合配置,会生成Mapper接口,并适当添加合适的Annotation,但是XML会生成在XML中;
3,XMLMAPPER:会生成Mapper接口,接口完全依赖XML;
注意,如果context是MyBatis3Simple:只支持ANNOTATEDMAPPER和XMLMAPPER \-->
<javaClientGenerator targetPackage\="com.\_520it.mybatis.mapper" type\="ANNOTATEDMAPPER" targetProject\="src/main/java"\>
<!-- 在targetPackage的基础上,根据数据库的schema再生成一层package,最终生成的类放在这个package下,默认为false \-->
<property name\="enableSubPackages" value\="true"/>
<!-- 可以为所有生成的接口添加一个父接口,但是MBG只负责生成,不负责检查
<property name="rootInterface" value=""/> \-->
</javaClientGenerator\>
<!-- 选择一个table来生成相关文件,可以有一个或多个table,必须要有table元素
选择的table会生成一下文件:
1,SQL map文件
2,生成一个主键类;
3,除了BLOB和主键的其他字段的类;
4,包含BLOB的类;
5,一个用户生成动态查询的条件类(selectByExample, deleteByExample),可选;
6,Mapper接口(可选)
tableName(必要):要生成对象的表名;
注意:大小写敏感问题。正常情况下,MBG会自动的去识别数据库标识符的大小写敏感度,在一般情况下,MBG会
根据设置的schema,catalog或tablename去查询数据表,按照下面的流程:
1,如果schema,catalog或tablename中有空格,那么设置的是什么格式,就精确的使用指定的大小写格式去查询;
2,否则,如果数据库的标识符使用大写的,那么MBG自动把表名变成大写再查找;
3,否则,如果数据库的标识符使用小写的,那么MBG自动把表名变成小写再查找;
4,否则,使用指定的大小写格式查询;
另外的,如果在创建表的时候,使用的""把数据库对象规定大小写,就算数据库标识符是使用的大写,在这种情况下也会使用给定的大小写来创建表名;
这个时候,请设置delimitIdentifiers="true"即可保留大小写格式;
可选:
1,schema:数据库的schema;
2,catalog:数据库的catalog;
3,alias:为数据表设置的别名,如果设置了alias,那么生成的所有的SELECT SQL语句中,列名会变成:alias\_actualColumnName
4,domainObjectName:生成的domain类的名字,如果不设置,直接使用表名作为domain类的名字;可以设置为somepck.domainName,那么会自动把domainName类再放到somepck包里面;
5,enableInsert(默认true):指定是否生成insert语句;
6,enableSelectByPrimaryKey(默认true):指定是否生成按照主键查询对象的语句(就是getById或get);
7,enableSelectByExample(默认true):MyBatis3Simple为false,指定是否生成动态查询语句;
8,enableUpdateByPrimaryKey(默认true):指定是否生成按照主键修改对象的语句(即update);
9,enableDeleteByPrimaryKey(默认true):指定是否生成按照主键删除对象的语句(即delete);
10,enableDeleteByExample(默认true):MyBatis3Simple为false,指定是否生成动态删除语句;
11,enableCountByExample(默认true):MyBatis3Simple为false,指定是否生成动态查询总条数语句(用于分页的总条数查询);
12,enableUpdateByExample(默认true):MyBatis3Simple为false,指定是否生成动态修改语句(只修改对象中不为空的属性);
13,modelType:参考context元素的defaultModelType,相当于覆盖;
14,delimitIdentifiers:参考tableName的解释,注意,默认的delimitIdentifiers是双引号,如果类似MYSQL这样的数据库,使用的是\`(反引号,那么还需要设置context的beginningDelimiter和endingDelimiter属性)
15,delimitAllColumns:设置是否所有生成的SQL中的列名都使用标识符引起来。默认为false,delimitIdentifiers参考context的属性
注意,table里面很多参数都是对javaModelGenerator,context等元素的默认属性的一个复写; \-->
<table tableName\="userinfo" \>
<!-- 参考 javaModelGenerator 的 constructorBased属性\-->
<property name\="constructorBased" value\="false"/>
<!-- 默认为false,如果设置为true,在生成的SQL中,table名字不会加上catalog或schema; \-->
<property name\="ignoreQualifiersAtRuntime" value\="false"/>
<!-- 参考 javaModelGenerator 的 immutable 属性 \-->
<property name\="immutable" value\="false"/>
<!-- 指定是否只生成domain类,如果设置为true,只生成domain类,如果还配置了sqlMapGenerator,那么在mapper XML文件中,只生成resultMap元素 \-->
<property name\="modelOnly" value\="false"/>
<!-- 参考 javaModelGenerator 的 rootClass 属性
<property name="rootClass" value=""/> \-->
<!-- 参考javaClientGenerator 的 rootInterface 属性
<property name="rootInterface" value=""/> \-->
<!-- 如果设置了runtimeCatalog,那么在生成的SQL中,使用该指定的catalog,而不是table元素上的catalog
<property name="runtimeCatalog" value=""/> \-->
<!-- 如果设置了runtimeSchema,那么在生成的SQL中,使用该指定的schema,而不是table元素上的schema
<property name="runtimeSchema" value=""/> \-->
<!-- 如果设置了runtimeTableName,那么在生成的SQL中,使用该指定的tablename,而不是table元素上的tablename
<property name="runtimeTableName" value=""/> \-->
<!-- 注意,该属性只针对MyBatis3Simple有用;
如果选择的runtime是MyBatis3Simple,那么会生成一个SelectAll方法,如果指定了selectAllOrderByClause,那么会在该SQL中添加指定的这个order条件; \-->
<property name\="selectAllOrderByClause" value\="age desc,username asc"/>
<!-- 如果设置为true,生成的model类会直接使用column本身的名字,而不会再使用驼峰命名方法,比如BORN\_DATE,生成的属性名字就是BORN\_DATE,而不会是bornDate \-->
<property name\="useActualColumnNames" value\="false"/>
<!-- generatedKey用于生成生成主键的方法,
如果设置了该元素,MBG会在生成的<insert>元素中生成一条正确的<selectKey>元素,该元素可选
column:主键的列名;
sqlStatement:要生成的selectKey语句,有以下可选项:
Cloudscape:相当于selectKey的SQL为: VALUES IDENTITY\_VAL\_LOCAL()
DB2 :相当于selectKey的SQL为: VALUES IDENTITY\_VAL\_LOCAL()
DB2\_MF :相当于selectKey的SQL为:SELECT IDENTITY\_VAL\_LOCAL() FROM SYSIBM.SYSDUMMY1
Derby :相当于selectKey的SQL为:VALUES IDENTITY\_VAL\_LOCAL()
HSQLDB :相当于selectKey的SQL为:CALL IDENTITY()
Informix :相当于selectKey的SQL为:select dbinfo('sqlca.sqlerrd1') from systables where tabid=1
MySql :相当于selectKey的SQL为:SELECT LAST\_INSERT\_ID()
SqlServer :相当于selectKey的SQL为:SELECT SCOPE\_IDENTITY()
SYBASE :相当于selectKey的SQL为:SELECT @@IDENTITY
JDBC :相当于在生成的insert元素上添加useGeneratedKeys="true"和keyProperty属性
<generatedKey column="" sqlStatement=""/> \-->
<!-- 该元素会在根据表中列名计算对象属性名之前先重命名列名,非常适合用于表中的列都有公用的前缀字符串的时候,
比如列名为:CUST\_ID,CUST\_NAME,CUST\_EMAIL,CUST\_ADDRESS等;
那么就可以设置searchString为"^CUST\_",并使用空白替换,那么生成的Customer对象中的属性名称就不是
custId,custName等,而是先被替换为ID,NAME,EMAIL,然后变成属性:id,name,email;
注意,MBG是使用java.util.regex.Matcher.replaceAll来替换searchString和replaceString的,
如果使用了columnOverride元素,该属性无效;
<columnRenamingRule searchString="" replaceString=""/> \-->
<!-- 用来修改表中某个列的属性,MBG会使用修改后的列来生成domain的属性;
column:要重新设置的列名;
注意,一个table元素中可以有多个columnOverride元素哈~ \-->
<columnOverride column\="username"\>
<!-- 使用property属性来指定列要生成的属性名称 \-->
<property name\="property" value\="userName"/>
<!-- javaType用于指定生成的domain的属性类型,使用类型的全限定名
<property name="javaType" value=""/> \-->
<!-- jdbcType用于指定该列的JDBC类型
<property name="jdbcType" value=""/> \-->
<!-- typeHandler 用于指定该列使用到的TypeHandler,如果要指定,配置类型处理器的全限定名
注意,mybatis中,不会生成到mybatis-config.xml中的typeHandler
只会生成类似:where id = #{id,jdbcType=BIGINT,typeHandler=com.\_520it.mybatis.MyTypeHandler}的参数描述
<property name="jdbcType" value=""/> \-->
<!-- 参考table元素的delimitAllColumns配置,默认为false
<property name="delimitedColumnName" value=""/> \-->
</columnOverride\>
<!-- ignoreColumn设置一个MGB忽略的列,如果设置了改列,那么在生成的domain中,生成的SQL中,都不会有该列出现
column:指定要忽略的列的名字;
delimitedColumnName:参考table元素的delimitAllColumns配置,默认为false
注意,一个table元素中可以有多个ignoreColumn元素
<ignoreColumn column="deptId" delimitedColumnName=""/> \-->
</table\>
</context>
</generatorConfiguration>

单例模式
引言
在软件开发中,单例模式(Singleton Pattern) 是一种常见的设计模式,用于确保一个类只有一个实例,并提供一个全局访问点。它适用于需要严格控制资源访问的场景,例如数据库连接池、配置管理器或任务调度器等。本文将详细介绍单例模式的核心思想,并展示其在 **C#、Python、Golang 中的实现方式。
单例模式的主要特点包括:
- 唯一性:类只有一个实例对象
- 自创建:类自行创建自己的实例
- 全局访问:提供一个全局访问点来获取该实例
特点
- 唯一性:类自身负责创建和管理实例。
- 延迟加载:实例通常在第一次使用时创建(懒汉式)。
- 线程安全:在多线程环境中需确保实例的唯一性。
- 不可克隆/序列化:避免通过克隆或反序列化创建新实例。
单例模式的实现方式
C# 实现
C# 中的单例模式通常通过 双重检查锁定(Double-Check Locking) 实现,以确保线程安全和延迟加载。
1 | public sealed class Singleton |
饿汉式(立即加载)
1 | public sealed class Singleton |
Python 实现
Python 的模块天然支持单例,但也可以通过类实现。以下是一个线程安全的懒汉式实现:
1 | import threading |
饿汉式(模块级单例)
1 | # singleton.py |
装饰器实现
1 | def singleton(cls): |
Golang 实现
1 | package main |
饿汉式
1 | package main |
单例模式的优缺点
优点
- 控制实例数量:确保全局唯一性,避免资源浪费。
- 灵活扩展:可通过子类化或组合模式扩展功能。
- 全局访问:简化了对共享资源的访问。
缺点
- 违反单一职责原则:类负责管理自己的实例,增加了耦合。
- 测试困难:全局状态可能导致单元测试难以隔离。
- 生命周期管理:实例与程序生命周期一致,可能占用过多内存。.
应用场景
- 资源管理器:如文件系统、数据库连接池。
- 配置中心:全局配置对象,避免重复加载配置。
- 缓存服务:单点缓存,减少内存开销。
- 日志记录器:统一日志输出,避免多线程冲突。
总结
单例模式是一种简单但强大的设计模式,适用于需要严格控制实例数量的场景。不同编程语言的实现方式各有特色:
- C# 通过
lock和volatile保证线程安全。 - Python 可利用模块的天然单例特性。
- Golang 使用
sync.Once实现原子初始化。 - C/C++ 通过静态局部变量或互斥锁实现线程安全。
实现要点总结:
- 私有构造函数:防止外部直接实例化
- 静态实例变量:保存唯一的实例
- 全局访问点:提供获取实例的静态方法
- 线程安全:在多线程环境下需要考虑线程安全问题
选择建议:
- 懒汉式:适用于实例创建开销较大,且可能不被使用的场景
- 饿汉式:适用于实例创建开销小,且一定会被使用的场景
- 双重检查锁定:适用于需要兼顾性能和线程安全的场景
在实际开发中,需根据语言特性和具体需求选择合适的实现方式,同时注意避免过度使用单例模式,以免引入全局状态带来的复杂性。
Java&Quartz实现任务调度
1.Quartz的作用
定时自动执行任务
2.预备
相关包官方网站
1 | quartz2.2.1 |
POM文件
1 | <dependency> |
3.Quartz核心
3.1.Job接口
被调度的任务,只有一个方法execute(JobExecutionContext xontext),Job运行时的信息保存在JobDataMap中
3.2.JobDetail类
实现Job接口,用来描述Job的相关信息,包含Name,Group,JobDataMap等
3.3 JobExecutionContext类
定时程序执行的run-time的上下文环境,用于得到Job的名字、配置的参数等
3.3 JobDataMap类
用来描述一个作业的参数,参数可以为金和基本类型或者某个对象的引用
3.3 JobListener接口
监听作业状态
3.3 TriggaerListener接口
监听触发器状态
3.3 JobStore
3.3.Tigger抽象类
触发器,描述执行Job的触发规则,有SimpleTrigger和CronTrigger两个子类
3.3.1.SimpleTrigger类
继承自Trigger类,每隔xx毫秒/秒执行一次,主要实现固定一次或者固定时间周期类任务的触发
3.3.2.CronTrigger类
继承自Trigger类,使用Cron表达式,实现各种复杂时间规则调度方案,如每天的某个时间,或每周的某几天触发执行之类
3.4.Calendar包
一些日历特定时间点的集合,包内包含以下几个类
3.4.1 BaseCalendar类
3.4.2 AnnualCalendar类
排除每一年中指定的一天或者多天
3.4.3 CalendarComparator类
3.4.4 CronCalendar类
使用表达式排除某时间段不执行
3.4.5 DailyCalendar类
指定的时间范围内每天不执行
3.4.6 HolidayCalendar类
排除节假日
3.4.7 MonthlyCalendar类
配出月份中的数天
3.4.8 WeeklyCalendar类
排除没周中的一天或者多天
3.5.Scheduler类
任务调度器,代表一个Quartz独立容器。
Scheduler可以将JobDetail和Trigger绑定,当Trigger触发时,对应的Job就会被执行,Job和Trigger是1:n(一对多)的关系
3.6Misfire类
错误的任务,本该执行单没有执行的任务调度
4.实现
1.单任务实现
1.定义一个任务,新建任务类继承自Job类
1 | package com; |
2.新建类执行这个任务(SimpleTrigger)
1 | package com; |
2.多任务实现
- 测试任务类
新建两个DemoJonOne和DemoJobTwo,都实现Job接口,内容如下
1 | @Override |
2.新建QuartzUtil类,内容如下
1 | package com; |
以上方法属于手动调用,如果是web项目中就不同了
添加POM
1 | <dependency> |
1 | package servlet; |
2.注册servlet
1 | <servlet> |
3.复杂规则任务调度(CronTrigger)
在每分钟的1-30秒执行示例
1 | package com; |
5.Cron表达式
规则
格式
1 | s M h d m w [y] |
s:seconds,取值0-59,允许- * /;
M:minutes,取值0-59,允许- * /;
h:hour,取值0-23,允许- * /;
d:day of month,取值1-31,允许- * ? / L W;
m:month,取值1-12/JAN-DEC,允许- * /;
w:day of week,取值1-7/SUN-SAT,允许- * ? / L #;
y:year,可选,取值empty、1970-2099,允许- * /;
符号解释
、 指定枚举值,如在秒字段使用10、12,则表示只有第10秒和第12秒执行
- 指定区间范围,配合使用,如在小时字段使用10-12,表示在10、11、12时都会触发
* 代表所有值,单独使用,如在秒字段使用,表示每秒触发
? 代表不确定值,单独使用,不用关心的值
/ 用于递增触发,配合使用,n/m,从n开始,每次增加m,如在秒字段设置5/15,表示从第5秒开始,每15秒触发一次
L 表示最后,单独使用,如在秒字段使用,代表第59秒触发,如果在前面加上数字,则表示该数据的最后一个,如在周字段使用6L,则表示本月最后一个周五
W 表示最近的工作日,不会跨月,比如30W,30号是周六,则不会顺延至下周一来执行,如在月字段使用15W,则表示到本月15日最近的工作日(周一到周五)
# 用来指定x的第n个工作日,如在周字段使用6#3则表示该月的第三个星期五
月取值
一月:JAN/0
二月:FEB/1
三月:MAR/2
四月:APR/3
五月:MAY/4
六月:JUN/5
七月:JUL/6
八月:AUG/7
九月:SEP/8
十月:OCT/9
十一月:NOV/10
十二月:DEC/11
周取值
周日:SUN/1
周一:MON/2
周二:TUE/3
周三:WED/4
周四:THU/5
周五:FRI/6
周六:SAT/7
示例
1 | 0/20 * * * * ? 每20秒执行一次 |
6.Spring整合Quartz
需要Spring-context-support包支持,POM如下
1 | <dependency> |
新建两种Job测试类–>DemoSimpleJob类和DemoCronJob类,并继承自QuartzJobBean,代码如下
1 | package com; |
配置spring bean如下
1 | <?xml version="1.0" encoding="UTF-8"?> |
启动
1 | package com; |
有待补充
C#使用EmguCV实现视频读取和播放,及多个视频一起播放的问题
- WinForm程序
1)第一种方法,使用委托:
1 |
|
2)第二种方法,使用匿名委托
1 | private void SetText(Object obj) |
这里说一下BeginInvoke和Invoke和区别:BeginInvoke会立即返回,Invoke会等执行完后再返回。
- WPF程序
1)可以使用Dispatcher线程模型来修改
如果是窗体本身可使用类似如下的代码:
this.lblState.Dispatcher.Invoke(new Action(delegate
{
this.lblState.Content = “状态:” + this._statusText;
}));
那么假如是在一个公共类中弹出一个窗口、播放声音等呢?这里我们可以使用:System.Windows.Application.Current.Dispatcher,如下所示

System.Windows.Application.Current.Dispatcher.Invoke(new Action(() =>
{
if (path.EndsWith(“.mp3”) || path.EndsWith(“.wma”) || path.EndsWith(“.wav”))
{
_player.Open(new Uri(path));
_player.Play();
}
}));
关键问题:多个视频同时播放,以上几种方法不足以解决,多个视频播放中主界面卡死和播放显示刷新不了的问题。
目前笔者的解决方法是
pinturebox.CreateGraphics().DrawImage(imgSrc.Bitmap, new System.Drawing.Rectangle(0, 0, pinturebox.Width, pinturebox.Height));
EmguCV中的Capture类可以完成视频文件的读取,并捕捉每一帧,可以利用Capture类完成实现WinForm中视频检测跟踪环境的搭建。本文只实现最简陋的WinForm + EmguCV上的avi文件读取和播放框架,复杂的检测和跟踪算法在之后添加进去。
这里使用WinForm实现视频的播放,主要是PictureBox类,它是支持基于事件的异步模式的典型组件,不使用EmguCV自带的UI控件等。
图1.效果图
直接在UI线程中完成视频的播放的话整个程序只有一个线程,由于程序只能同步执行,播放视频的时候UI将停止响应用户的输入,造成界面的假死。所以视频的播放需要实现异步模式。主要有三种方法:第一是使用异步委托;第二种是使用BackgroundWorker组件;最后一种就是使用多线程(不使用CheckForIllegalCrossThreadCalls =false的危险做法)。
Windows窗体控件,唯一可以从创建它的线程之外的线程中调用的是Invoke()、BegionInvoke()、EndInvoke()方法和InvokeRequired属性。其中BegionInvoke()、EndInvoke()方法是Invoke()方法的异步版本。这些方法会切换到创建控件的线程上,以调用赋予一个委托参数的方法,该委托参数可以传递给这些方法。
(一) 使用多线程
首先定义监控的类及其对应的事件参数类和异常类:
判断是否继续执行的布尔型成员会被调用线程改变,因此声名为volatile,不进行优化。
1 | /// <summary> |
UI线程中启动播放线程:
声明:
1 | /// <summary> |
读入视频文件:
1 | captureSurveillance = new Capture(this.videoFilePath); |
播放视频文件:
UI线程中响应监控类的事件:
定义异步调用的委托:
添加事件委托:
1 | this.surveillant.FrameRefresh += OnRefreshFrame; |
以下方法中都是由监控线程中的事件委托方法,应该使用BeginInvoke方法,这样可以优雅的结束线程,如果使用Invoke方法,则调用方式为同步调用,此时如果使用Thread.Join()方法终止线程将引发死锁(正常播放没有问题),Thread.Join()方法的使用使调用线程阻塞等待当前线程完成,在这里即UI线程阻塞等待监控线程完成,而监控线程中又触发UI线程中pictureBox的刷新,使用Invoke方法就造成了监控线程等待UI线程刷新结果,而UI线程已经阻塞,形成了死锁。死锁时只能用Thread.Abort()方法才能结束线程。或者直接强制结束应用程序。
使用BeginInvoke方法时为异步调用,监控线程不等待刷新结果直接继续执行,可以正常结束。结束后UI才进行刷新,不会造成死锁。
图2.线程关系
1 | /// <summary> |
(二) 使用异步委托
创建线程的一个更简单的方法是定义一个委托,并异步调用它。委托是方法的类型安全的引用。Delegate类还支持异步地调用方法。在后台,Delegate类会创建一个执行任务的线程。
1 | // asynchronous by using a delegate |
(三) 使用BackgroundWorker组件
BackgroundWorker类是异步事件的一种实现方案,异步组件可以选择性的支持取消操作,并提供进度信息。RunWorkerAsync()方法启动异步调用。CancelAsync()方法取消。
图3.BackgroundWorker组件
1 | /// <summary> |
在 WPF 中使用 Path 路径
在 WPF 中总会修改 Button 的 Style,比如一个自定义的 Close 按钮。刚入门的可能会用一张 PNG 格式的图片来做这个按钮的 Icon,但这个是不优雅的。而且你要改的时候还得去操作文件,想想都痛苦。
但是很多人苦于不知道去哪里获取 Path,当然网上已经有不少使用 Photoshop 获取图片的 Path ,但如果图片的质量不好,获取的 Path 歪歪曲曲的也不好看,更何况在这之前你还得会使用 Photoshop。
现在分享一个我经常使用的解决方案,阿里巴巴矢量图,这上面可以说有海量的图标可以用到。
流程:
1,进入 阿里巴巴矢量图 并搜索你想要的图标
2,下载 Icon 时使用 SVG 下载
3,用记事本或文本编辑器打开,标签 Path 下的 d 属性就是 Path 的 Data 数据(很多复杂一点的 Icon 可能是多个 Data 组成,使用时只要用空格把几个 Data 隔开就行)
例子:
<svg t=“1491032725422” class=“icon” style=“” viewBox=“0 0 1024 1024” version=“1.1” xmlns=“http://www.w3.org/2000/svg“ p-id=“2372” xmlns:xlink=“http://www.w3.org/1999/xlink“ width=“248” height=“248”>
<defs>
<style type=“text/css”></style>
</defs>
<path d=“M503.2868 510.9903m-349.4226 0a341.233 341.233 0 1 0 698.8452 0 341.233 341.233 0 1 0-698.8452 0Z” p-id=“2373”></path>
<path d=“M106.1386 263.9677a110 100 0 1 1 121.6696 248.2668Z” p-id=“2374”></path>
</svg>
在WPF中使用时:
<Path Data=“M503.2868 510.9903m-349.4226 0a341.233 341.233 0 1 0 698.8452 0 341.233 341.233 0 1 0-698.8452 0Z M106.1386 263.9677a110 100 0 1 1 121.6696 248.2668Z”/>
Data 也可以作为资源放在独立的资源字典里,使用的 Geometry 标签
<Geometry x:Key=“logo”>M503.2868 510.9903m-349.4226 0a341.233 341.233 0 1 0 698.8452 0 341.233 341.233 0 1 0-698.8452 0Z M106.1386 263.9677a110 100 0 1 1 121.6696 248.2668Z</Geometry>
XAML:
<Path Data=“{StaticResource logo}” Fill=“White” Stretch=“Fill” Stroke=“White” StrokeThickness=“1.5” />
Net作业调度(五)—quartz.net动态添加job设计
介绍
在实际项目使用中quartz.net中,都希望有一个管理界面可以动态添加job,而避免每次都要上线发布。
也看到有园子的同学问过。这里就介绍下实现动态添加job的几种方式, 也是二次开发的核心模块。
阅读目录:
传统方式
继承IJob,实现业务逻辑,添加到scheduler。
public class MonitorJob : IJob
{ public void Execute(IJobExecutionContext context)
{ //do something
Console.WriteLine(“test”);
}
} //var job = JobBuilder.Create
也可以使用CrystalQuartz远程管理暂停取消。之前的博客CrystalQuartz远程管理(二)。
框架反射方式
这种方式需要定义一套接口框架。 比如:
interface IcustomJob
{ void Excute(string context); void Failed(string error); void Complete(string msg);
}
1:当我们写job时同一实现这个框架接口,类库形式。
2:写完后编译成DLL,上传到我们的作业执行节点。
3:在执行节点中,通过反射拿到DLL的job信息。
4:然后构建quartz的job,添加到scheduler。
这种方式缺点: 耦合性太高,开发量较大。 优点:集中式管理。
系统结构如图:
%E2%80%94quartz.net%E5%8A%A8%E6%80%81%E6%B7%BB%E5%8A%A0job%E8%AE%BE%E8%AE%A1/181839415731718.png)
进程方式
这个方式和windows任务计划类似。
1:使用方编写自己的job,无需实现任何接口,可执行应用程序形式。
2:将程序发送到执行节点,由执行节点起进程调用job程序。
执行节点调用,示例如下:
public class ConsoleJob:IJob
{ public void Execute(IJobExecutionContext context)
{
JobDataMap dataMap = context.JobDetail.JobDataMap; string content = dataMap.GetString(“jobData”); var jd = new JavaScriptSerializer().Deserialize
Process p \= new Process();
p.StartInfo.UseShellExecute \= true;
p.StartInfo.FileName \= jd.Path;
p.StartInfo.Arguments \= jd.Parameters; //空格分割
p.StartInfo.WindowStyle = ProcessWindowStyle.Minimized;
p.Start();
}
}
这种方式相对来说: 耦合性中等,执行节点和job相互不关心,没有依赖,开发量较小。
系统结构如图:
%E2%80%94quartz.net%E5%8A%A8%E6%80%81%E6%B7%BB%E5%8A%A0job%E8%AE%BE%E8%AE%A1/181849136677228.png)
URL方式
URL方式和第三种类似,不过调用的不在是执行程序,而是URL。
1: 使用方在网页或服务中,实现业务逻辑。
2: 然后将Url,交给执行节点post或get执行。
执行节点调用,示例如下:
public class HttpJob : IJob
{ public void Execute(IJobExecutionContext context)
{ var dataMap = context.JobDetail.JobDataMap; var content = dataMap.GetString(“jobData”); var jd = new JavaScriptSerializer().Deserialize
jd.Parameters = string.Empty; if (jd.Timeout == 0)
jd.Timeout = 5*60; var result = RequestHelper.Post(jd.Url, jd.ContentType, jd.Timeout, jd.Parameters, jd.heads);
}
}
这种方式耦合比较低,使用方不需要单独写应用程序了,和平常业务开发一样。
执行节点的职权,仅仅作为一个触发器。
有2点需要注意的是:
1:请求URL时,注意双方约定token加密,防止非执行节点执行调用。
2:使用方,如果有耗时操作,建议异步执行。
系统结构如图:
%E2%80%94quartz.net%E5%8A%A8%E6%80%81%E6%B7%BB%E5%8A%A0job%E8%AE%BE%E8%AE%A1/181853563397152.png)
框架配置方式
1:使用方直接使用quartz.net框架,实现自己的job。从管理方拉取执行节点配置,然后自行管理执行节点。
2:使用方也可以暴露端口给管理方,以实现监控,修改配置。
这种形式,耦合性最低。是把管理方当成一个配置中心。 ps:几乎和传统方式+CrystalQuartz一样了。
%E2%80%94quartz.net%E5%8A%A8%E6%80%81%E6%B7%BB%E5%8A%A0job%E8%AE%BE%E8%AE%A1/181858070583283.png)
通过context.JobDetail.JobDataMap,可以保存job的需要的信息。
本篇介绍主流的几种实现方案,供大家参考使用。