95992828九五至尊2

单点登录中间件

三月 19th, 2019  |  882828九五至尊手机版

本章将要和我们享受的是一个单点登录中间件,中间件听起来高深其实那里只是吧单点登录要用到的逻辑和拍卖流程封装成了多少个办法而已,暗中同意扶助采纳redis服务保存session的章程,也能够利用参数Func<>措施来做自定义session存款和储蓄操作的方式,就毫无自个儿暗中认可提供的redis存储的办法了;要说本章内容的源于,其实是本身在从前的ShenNiu.MVC管住种类中进入了近年来做的调查问卷模块,那一个问卷调查和ShenNiu.MVC不是3个站点,然而自身的问卷调查系统可定在保卫安全问卷或难题的时候须求登录人的消息,小编又不想再独自弄一套账号方面包车型地铁顺序了,所以就选拔那种单点登录形式,以此来提供调查问卷的所须求的用户音讯,以及为了尽快的后天祥和写的有些模块也需求管理用户音讯的话,就能省略掉用户模块了,不得不说单点登录在那儿发挥的效益之大;本章内容希望大家能够欣赏,也指望各位多多”扫码接济”和”推荐”多谢!若是您想要和我们交换越多mvc相关音讯能够来Ninesky框架作者:洞庭夕照
内定的法定群 428310563交换;

本章将要和豪门享受的是三个单点登录中间件,中间件听起来高深其实那里只是吧单点登录要用到的逻辑和处理流程封装成了多少个法子而已,暗中同意援救选用redis服务保存session的章程,也足以动用参数Func<>方式来做自定义session存款和储蓄操作的格局,就无须自个儿私下认可提供的redis存储的办法了;要说本章内容的发源,其实是本身在原先的ShenNiu.MVC管住种类中进入了不久前做的调查研讨问卷模块,这一个问卷调查和ShenNiu.MVC不是3个站点,然而笔者的问卷调查系统可定在保卫安全问卷或难点的时候供给登录人的消息,笔者又不想再独自弄一套账号方面包车型地铁先后了,所以就选用那种单点登录方式,以此来提供调查问卷的所要求的用户新闻,以及为了尽早的今后友好写的某些模块也急需管理用户消息的话,就能省略掉用户模块了,不得不说单点登录在此刻发布的遵循之大;本章内容希望我们能够欣赏,也期望各位多多”扫码支持”和”推荐”多谢!假诺您想要和我们调换更加多mvc相关音信能够来Ninesky框架作者:洞庭夕照
钦点的合法群 428310563沟通;

 

 

» 单点登录验证手画示例图

» 单点登录验证手画示例图

» ShenNiuApi.SDK封装中间件代码

» ShenNiuApi.SDK封装中间件代码

» 调查问卷系统使用中间件示例

» 调查问卷系统使用中间件示例

» 推广调查问卷系统

» 推广调查问卷系统

 

 

下边一步叁个脚印的来分享:

下边一步五个脚印的来分享:

» 单点登录验证手画示例图

» 单点登录验证手画示例图

第贰,咋们要做三个简短的单点登录成效,须要知道其执行的流程和平运动转的法则,那里将绘身绘色重点建议本人以为关键的地点,先上一幅手工业图:

率先,咋们要做1个不难的单点登录功效,须求精通其实践的流程和周转的原理,那里将涉笔成趣重点提议本人认为首要的地点,先上一幅手工业图:

882828九五至尊手机版 1

882828九五至尊手机版 2

看起来图画的不是相当美丽,可是作者想发挥的情致感觉依然表明清楚了;作为四个单点登录验证模块,最重大的流程有:

看起来图画的不是很为难,然而自个儿想表明的情致感觉依然表明清楚了;作为贰个单点登录验证模块,最要害的流水生产线有:

1.
未登录时:提供统一登录入口=》去数据库验证账号正确性=》存款和储蓄会话session(那里运用redis存款和储蓄token和用户登陆音讯,利用其数量过期策略充当session会电话机制)=》重定向到redirectUrl钦命的地址

1.
未登录时:提供统一登录入口=》去数据库验证账号正确性=》存款和储蓄会话session(那里运用redis存款和储蓄token和用户登陆消息,利用其数额过期策略充当session会电话机制)=》重定向到redirectUrl钦定的地方

2.
已报到时:获取站点的cookie存款和储蓄的sessionId(token)=》调用验证token有效接口=》那里有二种状态(a,b)

2.
已登录时:获取站点的cookie存款和储蓄的sessionId(token)=》调用验证token有效接口=》那里有二种状态(a,b)

    a)
有效token=》获取登录用户的session存款和储蓄的新闻(redis存款和储蓄的value音信)

    a)
有效token=》获取登录用户的session存款和储蓄的音信(redis存款和储蓄的value消息)

    b) 无效token=》重回无效音信,构造登录入口地址

    b) 无效token=》重回无效消息,构造登录入口地址

透过上面分析,大概的流水生产线应该很强烈了上边大家就来看包装的代码;

透过下边分析,差不多的流程应该很显明了上面大家就来看包装的代码;

 

 

» ShenNiuApi.SDK封装中间件代码

» ShenNiuApi.SDK封装中间件代码

此处要看的是中间件的3个法子:SsoMiddleWareServer(登录入口操作),SsoMiddleWareClient(Token验证及取得登录信息),SsoMiddleWareLoginOut(注销操作);那里作者早就把措施打包放到了nuget上: Install-Package
ShenNiuApi.SDK ,只必要下载最新的sdk,就能轻轻松松帮你实现一个单点登录架构,下边来看具体的代码;

那边要看的是中间件的一个主意:SsoMiddleWareServer(登录入口操作),SsoMiddleWareClient(Token验证及取得登录音讯),SsoMiddleWareLoginOut(注销操作);那里小编已经把措施打包放到了nuget上: Install-Package
ShenNiuApi.SDK ,只供给下载最新的sdk,就能轻轻松松帮你落成3个单点登录架构,下边来看现实的代码;

SsoMiddleWareServer(登录入口操作):

SsoMiddleWareServer(登录入口操作):

 1         /// <summary>
 2         /// 单点登录操作 SSOMiddleWare服务端(方法功能:
 3         /// 1.生成sessionId 
 4         /// 2.存储session到redis(60分钟失效)或者自定义sessionStoreFunc方法中 
 5         /// 3.构造带有token的重定向地址)
 6         /// 注:默认采用redis保存session,因此需要在conf中配置ReadAndWritePorts和OnlyReadPorts两个appSettings节点:
 7         /// ReadAndWritePorts在conf中配置格式如:pwd@ip:port,多个使用‘|’隔开       实例:shenniubuxing3@127.0.0.1:6377
 8         /// OnlyReadPorts在conf中配置格式如:pwd@ip:port,多个使用‘|’隔开              实例:shenniubuxing3@127.0.0.1:6377
 9         /// </summary>
10         /// <typeparam name="TUserBaseInfo">存储登录信息的对象</typeparam>
11         /// <param name="userBaseInfo">登录信息</param>
12         /// <param name="redirectUrl">重定向地址(注:格式应为http://或者https://;并经过UrlEncode转码后的地址;如果是同站点下面的话无需http://标记)</param>
13         /// <param name="token">执行方法无误后ref返回唯一的token(注:token生成规则是唯一的tokenKey+guid+时间戳)</param>
14         /// <param name="tokenKey">生成token的Key(默认:666666)</param>
15         /// <param name="sessionStoreFun">自定义session存储方法(提供自定义操作保存session的方法,覆盖默认的reids存储方式)</param>
16         /// <param name="timeOut">60(分钟)</param>
17         /// <returns>追加有token的重定向地址</returns>
18         public string SsoMiddleWareServer<TUserBaseInfo>(TUserBaseInfo userBaseInfo, string redirectUrl, ref string token, string tokenKey = "666666", Func<TUserBaseInfo, bool> sessionStoreFun = null, int timeOut = 60)
19             where TUserBaseInfo : class,new()
20         {
21             var returnUrl = string.Empty;
22             try
23             {
24                 //非空验证  
25                 if (string.IsNullOrWhiteSpace(redirectUrl) || userBaseInfo == null) { return returnUrl; }
26 
27                 //生成Token
28                 token = Md5Extend.GetSidMd5Hash(tokenKey);
29 
30                 // ShenNiuApi默认的Redis存储session
31                 if (sessionStoreFun == null && userBaseInfo != null)
32                 {
33                     if (!CacheRepository.Current(CacheType.RedisCache).SetCache<TUserBaseInfo>(token, userBaseInfo, timeOut, true)) { return returnUrl; }
34                 }
35                 else { if (!sessionStoreFun(userBaseInfo)) { return returnUrl; } }
36 
37                 //通域名站内系统登录
38                 if (!Uri.IsWellFormedUriString(redirectUrl, UriKind.Absolute))
39                 {
40                     returnUrl = redirectUrl;
41                     return returnUrl;
42                 }
43 
44                 #region 解析并构造跳转链接
45                 redirectUrl = HttpUtility.UrlDecode(redirectUrl);
46                 redirectUrl = redirectUrl.TrimEnd('&');
47                 redirectUrl = Regex.Replace(redirectUrl, "(&)?token=[^&]+(&)?", "");
48                 Uri uri = new Uri(redirectUrl);
49                 var queryStr = uri.Query;
50                 redirectUrl += queryStr.Contains('?') ? "" : "?";
51                 redirectUrl += string.IsNullOrWhiteSpace(queryStr.TrimStart('?')) ? "" : "&";
52                 returnUrl = string.Format("{0}token={1}", redirectUrl, token);
53                 #endregion
54             }
55             catch (Exception ex)
56             {
57                 throw new Exception(ex.Message);
58             }
59             finally
60             {
61                 if (string.IsNullOrWhiteSpace(returnUrl)) { token = string.Empty; }
62             }
63             return returnUrl;
64         }
 1         /// <summary>
 2         /// 单点登录操作 SSOMiddleWare服务端(方法功能:
 3         /// 1.生成sessionId 
 4         /// 2.存储session到redis(60分钟失效)或者自定义sessionStoreFunc方法中 
 5         /// 3.构造带有token的重定向地址)
 6         /// 注:默认采用redis保存session,因此需要在conf中配置ReadAndWritePorts和OnlyReadPorts两个appSettings节点:
 7         /// ReadAndWritePorts在conf中配置格式如:pwd@ip:port,多个使用‘|’隔开       实例:shenniubuxing3@127.0.0.1:6377
 8         /// OnlyReadPorts在conf中配置格式如:pwd@ip:port,多个使用‘|’隔开              实例:shenniubuxing3@127.0.0.1:6377
 9         /// </summary>
10         /// <typeparam name="TUserBaseInfo">存储登录信息的对象</typeparam>
11         /// <param name="userBaseInfo">登录信息</param>
12         /// <param name="redirectUrl">重定向地址(注:格式应为http://或者https://;并经过UrlEncode转码后的地址;如果是同站点下面的话无需http://标记)</param>
13         /// <param name="token">执行方法无误后ref返回唯一的token(注:token生成规则是唯一的tokenKey+guid+时间戳)</param>
14         /// <param name="tokenKey">生成token的Key(默认:666666)</param>
15         /// <param name="sessionStoreFun">自定义session存储方法(提供自定义操作保存session的方法,覆盖默认的reids存储方式)</param>
16         /// <param name="timeOut">60(分钟)</param>
17         /// <returns>追加有token的重定向地址</returns>
18         public string SsoMiddleWareServer<TUserBaseInfo>(TUserBaseInfo userBaseInfo, string redirectUrl, ref string token, string tokenKey = "666666", Func<TUserBaseInfo, bool> sessionStoreFun = null, int timeOut = 60)
19             where TUserBaseInfo : class,new()
20         {
21             var returnUrl = string.Empty;
22             try
23             {
24                 //非空验证  
25                 if (string.IsNullOrWhiteSpace(redirectUrl) || userBaseInfo == null) { return returnUrl; }
26 
27                 //生成Token
28                 token = Md5Extend.GetSidMd5Hash(tokenKey);
29 
30                 // ShenNiuApi默认的Redis存储session
31                 if (sessionStoreFun == null && userBaseInfo != null)
32                 {
33                     if (!CacheRepository.Current(CacheType.RedisCache).SetCache<TUserBaseInfo>(token, userBaseInfo, timeOut, true)) { return returnUrl; }
34                 }
35                 else { if (!sessionStoreFun(userBaseInfo)) { return returnUrl; } }
36 
37                 //通域名站内系统登录
38                 if (!Uri.IsWellFormedUriString(redirectUrl, UriKind.Absolute))
39                 {
40                     returnUrl = redirectUrl;
41                     return returnUrl;
42                 }
43 
44                 #region 解析并构造跳转链接
45                 redirectUrl = HttpUtility.UrlDecode(redirectUrl);
46                 redirectUrl = redirectUrl.TrimEnd('&');
47                 redirectUrl = Regex.Replace(redirectUrl, "(&)?token=[^&]+(&)?", "");
48                 Uri uri = new Uri(redirectUrl);
49                 var queryStr = uri.Query;
50                 redirectUrl += queryStr.Contains('?') ? "" : "?";
51                 redirectUrl += string.IsNullOrWhiteSpace(queryStr.TrimStart('?')) ? "" : "&";
52                 returnUrl = string.Format("{0}token={1}", redirectUrl, token);
53                 #endregion
54             }
55             catch (Exception ex)
56             {
57                 throw new Exception(ex.Message);
58             }
59             finally
60             {
61                 if (string.IsNullOrWhiteSpace(returnUrl)) { token = string.Empty; }
62             }
63             return returnUrl;
64         }

SsoMiddleWareClient(Token验证及取得登录音信):

SsoMiddleWareClient(Token验证及得到登录音讯):

 1   /// <summary>
 2         /// 单点登录操作 SSOMiddleWare客户端(方法功能:
 3         /// 1.验证客户端是否有sid或者url地址中带有最新的token 
 4         /// 2.获取服务端session的基本信息(注:默认直接读取服务端的redis库,同server方法一样需要配置对应的账号节点ReadAndWritePorts和OnlyReadPorts)
 5         /// 3.重新设置客户端cookie有效期和服务端存储session的有效期)
 6         /// </summary>
 7         /// <typeparam name="TUserBaseInfo">登陆用户信息对象</typeparam>
 8         /// <param name="httpContext">上下文HttpContext</param>
 9         /// <param name="ssoLoginUrl">sso统一登陆入口地址</param>
10         /// <param name="redirectUrl">待重定向的地址</param>
11         /// <param name="userBaseInfo">获取的登陆用户信息</param>
12         /// <param name="token">唯一token(即:sid)</param>
13         /// <param name="getOrsetSessionFun">自定义获取服务端用户信息方法并且同时要满足重新设置新的session有效时间</param>
14         /// <param name="sidName">cookie保存的sid名称</param>
15         /// <param name="timeOut">过期时间</param>
16         /// <returns></returns>
17         public string SsoMiddleWareClient<TUserBaseInfo>(HttpContext httpContext, string ssoLoginUrl, string redirectUrl, ref TUserBaseInfo userBaseInfo, ref string token, Func<string, int, TUserBaseInfo> getAndsetSessionFun = null, string sidName = "sid", int timeOut = 60)
18                where TUserBaseInfo : class,new()
19         {
20             var returnUrl = string.Empty;
21             try
22             {
23                 userBaseInfo = default(TUserBaseInfo);
24                 token = string.Empty;
25                 if (string.IsNullOrWhiteSpace(ssoLoginUrl) || string.IsNullOrWhiteSpace(redirectUrl) || string.IsNullOrWhiteSpace(sidName)) { return returnUrl; }
26 
27                 //设置过期后验证url串 
28                 returnUrl = string.Format("{0}?returnUrl={1}", ssoLoginUrl, HttpUtility.UrlEncode(redirectUrl));
29 
30                 //获取token
31                 var cookie = httpContext.Request.Cookies.Get(sidName);
32                 token = httpContext.Request.Params["token"];
33                 token = string.IsNullOrWhiteSpace(token) ? (cookie == null ? "" : cookie.Value) : token;
34                 if (string.IsNullOrWhiteSpace(token)) { return returnUrl; }
35 
36                 //获取用户基本信息
37                 if (getAndsetSessionFun != null)
38                 {
39                     userBaseInfo = getAndsetSessionFun(token, timeOut);
40                 }
41                 else
42                 {
43                     userBaseInfo = CacheRepository.Current(CacheType.RedisCache).GetCache<TUserBaseInfo>(token, true);
44                 }
45                 if (userBaseInfo == null)
46                 {
47                     //过期cookie,清空
48                     if (cookie != null)
49                     {
50                         cookie.Expires = DateTime.Now.AddDays(-1);
51                         httpContext.Response.SetCookie(cookie);
52                     }
53                     return returnUrl;
54                 }
55 
56                 //cookie被清除,需要重新设置
57                 if (cookie == null)
58                 {
59                     cookie = new HttpCookie(sidName, token);
60                     cookie.Expires = DateTime.Now.AddMinutes(timeOut);
61                     httpContext.Response.AppendCookie(cookie);
62                 }
63                 else
64                 {
65                     //登陆验证都成功后,需要重新设置cookie中的toke失效时间
66                     cookie.Value = token;
67                     cookie.Expires = DateTime.Now.AddMinutes(timeOut);
68                     httpContext.Response.SetCookie(cookie);
69                 }
70 
71                 //设置服务端session的失效时间
72                 if (getAndsetSessionFun == null)
73                 {
74                     CacheRepository.Current(CacheType.RedisCache).AddExpire(token, timeOut);
75                 }
76                 returnUrl = string.Empty;
77             }
78             catch (Exception ex)
79             {
80                 throw new Exception(ex.Message);
81             }
82             finally { if (!string.IsNullOrWhiteSpace(returnUrl)) { token = string.Empty; } }
83             return returnUrl;
84         }
 1   /// <summary>
 2         /// 单点登录操作 SSOMiddleWare客户端(方法功能:
 3         /// 1.验证客户端是否有sid或者url地址中带有最新的token 
 4         /// 2.获取服务端session的基本信息(注:默认直接读取服务端的redis库,同server方法一样需要配置对应的账号节点ReadAndWritePorts和OnlyReadPorts)
 5         /// 3.重新设置客户端cookie有效期和服务端存储session的有效期)
 6         /// </summary>
 7         /// <typeparam name="TUserBaseInfo">登陆用户信息对象</typeparam>
 8         /// <param name="httpContext">上下文HttpContext</param>
 9         /// <param name="ssoLoginUrl">sso统一登陆入口地址</param>
10         /// <param name="redirectUrl">待重定向的地址</param>
11         /// <param name="userBaseInfo">获取的登陆用户信息</param>
12         /// <param name="token">唯一token(即:sid)</param>
13         /// <param name="getOrsetSessionFun">自定义获取服务端用户信息方法并且同时要满足重新设置新的session有效时间</param>
14         /// <param name="sidName">cookie保存的sid名称</param>
15         /// <param name="timeOut">过期时间</param>
16         /// <returns></returns>
17         public string SsoMiddleWareClient<TUserBaseInfo>(HttpContext httpContext, string ssoLoginUrl, string redirectUrl, ref TUserBaseInfo userBaseInfo, ref string token, Func<string, int, TUserBaseInfo> getAndsetSessionFun = null, string sidName = "sid", int timeOut = 60)
18                where TUserBaseInfo : class,new()
19         {
20             var returnUrl = string.Empty;
21             try
22             {
23                 userBaseInfo = default(TUserBaseInfo);
24                 token = string.Empty;
25                 if (string.IsNullOrWhiteSpace(ssoLoginUrl) || string.IsNullOrWhiteSpace(redirectUrl) || string.IsNullOrWhiteSpace(sidName)) { return returnUrl; }
26 
27                 //设置过期后验证url串 
28                 returnUrl = string.Format("{0}?returnUrl={1}", ssoLoginUrl, HttpUtility.UrlEncode(redirectUrl));
29 
30                 //获取token
31                 var cookie = httpContext.Request.Cookies.Get(sidName);
32                 token = httpContext.Request.Params["token"];
33                 token = string.IsNullOrWhiteSpace(token) ? (cookie == null ? "" : cookie.Value) : token;
34                 if (string.IsNullOrWhiteSpace(token)) { return returnUrl; }
35 
36                 //获取用户基本信息
37                 if (getAndsetSessionFun != null)
38                 {
39                     userBaseInfo = getAndsetSessionFun(token, timeOut);
40                 }
41                 else
42                 {
43                     userBaseInfo = CacheRepository.Current(CacheType.RedisCache).GetCache<TUserBaseInfo>(token, true);
44                 }
45                 if (userBaseInfo == null)
46                 {
47                     //过期cookie,清空
48                     if (cookie != null)
49                     {
50                         cookie.Expires = DateTime.Now.AddDays(-1);
51                         httpContext.Response.SetCookie(cookie);
52                     }
53                     return returnUrl;
54                 }
55 
56                 //cookie被清除,需要重新设置
57                 if (cookie == null)
58                 {
59                     cookie = new HttpCookie(sidName, token);
60                     cookie.Expires = DateTime.Now.AddMinutes(timeOut);
61                     httpContext.Response.AppendCookie(cookie);
62                 }
63                 else
64                 {
65                     //登陆验证都成功后,需要重新设置cookie中的toke失效时间
66                     cookie.Value = token;
67                     cookie.Expires = DateTime.Now.AddMinutes(timeOut);
68                     httpContext.Response.SetCookie(cookie);
69                 }
70 
71                 //设置服务端session的失效时间
72                 if (getAndsetSessionFun == null)
73                 {
74                     CacheRepository.Current(CacheType.RedisCache).AddExpire(token, timeOut);
75                 }
76                 returnUrl = string.Empty;
77             }
78             catch (Exception ex)
79             {
80                 throw new Exception(ex.Message);
81             }
82             finally { if (!string.IsNullOrWhiteSpace(returnUrl)) { token = string.Empty; } }
83             return returnUrl;
84         }

SsoMiddleWareLoginOut(注销操作):

SsoMiddleWareLoginOut(注销操作):

 1  /// <summary>
 2         /// 单点登录操作 SSOMiddleWare 退出登陆
 3         /// </summary>
 4         /// <param name="httpContext">Http向下文</param>
 5         /// <param name="removeSession">自定义移除方法</param>
 6         /// <param name="sidName">cookie保存的sid名称</param>
 7         /// <returns>true或false</returns>
 8         public bool SsoMiddleWareLoginOut(HttpContext httpContext, Func<string, bool> removeSession = null, string sidName = "sid")
 9         {
10             var isfalse = true;
11             try
12             {
13                 if (string.IsNullOrWhiteSpace(sidName)) { sidName = "sid"; }
14 
15                 //获取cookie中的token
16                 var cookie = httpContext.Request.Cookies.Get(sidName);
17                 if (cookie == null) { return isfalse; }
18 
19                 //设置过期cookie(先过期cookie)
20                 var key = cookie.Value;
21                 cookie.Expires = DateTime.Now.AddDays(-1);
22                 httpContext.Response.SetCookie(cookie);
23 
24                 //移除session
25                 if (removeSession != null)
26                 {
27                     isfalse = removeSession(key);
28                 }
29                 else
30                 {
31                     isfalse = CacheRepository.Current(CacheType.RedisCache).Remove(key);
32                 }
33             }
34             catch (Exception ex)
35             {
36 
37                 throw new Exception(ex.Message);
38             }
39             return isfalse;
40         }
 1  /// <summary>
 2         /// 单点登录操作 SSOMiddleWare 退出登陆
 3         /// </summary>
 4         /// <param name="httpContext">Http向下文</param>
 5         /// <param name="removeSession">自定义移除方法</param>
 6         /// <param name="sidName">cookie保存的sid名称</param>
 7         /// <returns>true或false</returns>
 8         public bool SsoMiddleWareLoginOut(HttpContext httpContext, Func<string, bool> removeSession = null, string sidName = "sid")
 9         {
10             var isfalse = true;
11             try
12             {
13                 if (string.IsNullOrWhiteSpace(sidName)) { sidName = "sid"; }
14 
15                 //获取cookie中的token
16                 var cookie = httpContext.Request.Cookies.Get(sidName);
17                 if (cookie == null) { return isfalse; }
18 
19                 //设置过期cookie(先过期cookie)
20                 var key = cookie.Value;
21                 cookie.Expires = DateTime.Now.AddDays(-1);
22                 httpContext.Response.SetCookie(cookie);
23 
24                 //移除session
25                 if (removeSession != null)
26                 {
27                     isfalse = removeSession(key);
28                 }
29                 else
30                 {
31                     isfalse = CacheRepository.Current(CacheType.RedisCache).Remove(key);
32                 }
33             }
34             catch (Exception ex)
35             {
36 
37                 throw new Exception(ex.Message);
38             }
39             return isfalse;
40         }

各样方法的参数及功效,每行逻辑代码的都有注释,各位不妨研读下;那里要说的是种种方法都默许有操作redis存款和储蓄session的手续,由此能够看出其中间件暗许使用的是redis服务存款和储蓄session;

各样方法的参数及作用,每行逻辑代码的都有注释,各位不妨研读下;那里要说的是每一种方法都私下认可有操作redis存款和储蓄session的步骤,由此能够见到此中间件默许使用的是redis服务存款和储蓄session;

有人会问为啥会如此做,您单点登录难道最底部用的不是接口来操作登录或注脚的啊?那里考虑有这么一个实用场景,作为一个人中型小型型公司的职工来说,接触到服务器经常安插了全数集团的站点比如:站点1,站点2…即便域名不同只是都在一如既往台服务器上,再试想下一旦用redis来储存session会话,此刻是或不是就能认为本人这台服务器就有所间接待上访问redis的读写权限(当然若是redis服务也在那台服务器上的话就更不用说了),那自个儿直接在中间件中放到公共操作redis获取session,存款和储蓄session等操作是否都没难题,如此那般那大家还须要独自弄贰个session(token)验证的api么,没须求的事体(对于单点登录站点和重定向站点而言没要求),由此笔者就那样干了,嵌入贰个默许的redis操作哈哈(不服能够来辨);即便如此不得不考虑更加多的业务场景,万一登录账单和别的站点不在一个服务器(大概说不大概直接待上访问redis呢),这里在三当中间件方法参数中提供了三个Func<>参数,各类方法的Func<>代表额意思有点距离,各位能够看下注释;有了这么些自定义Func,中间件就能识别如若客户端有传递此办法,那么以Func为主,没有就使用私下认可的措施操作redis,这样允许使用者自定义方法扩张了使用者本人觉得调用token验证的api只怕别的合理的不二法门,那也确定保障了措施的通用性。

有人会问何故会那样做,您单点登录难道最底部用的不是接口来操作登录或证实的吗?那里考虑有那样三个实用场景,作为1人中型小型型集团的职员和工人来说,接触到服务器一般布置了整套集团的站点比如:站点1,站点2…就算域名不等同只是都在相同台服务器上,再试想下一旦用redis来囤积session会话,此刻是否就能认为作者那台服务器就有着直接待上访问redis的读写权限(当然倘诺redis服务也在那台服务器上的话就更不要说了),那作者一向在中间件中放置公共操作redis获取session,存款和储蓄session等操作是否都没难点,如此那般那咱们还索要单独弄二个session(token)验证的api么,没供给的思想政治工作(对于单点登录站点和重定向站点而言没须要),由此笔者就这么干了,嵌入二个暗中同意的redis操作哈哈(不服能够来辨);尽管如此不得不考虑更加多的业务场景,万一登录账单和其他站点不在三个服务器(也许说不能直接待上访问redis呢),那里在1当中间件方法参数中提供了二个Func<>参数,每一个方法的Func<>代表额意思有点距离,各位能够看下注释;有了那些自定义Func,中间件就能识别借使客户端有传递此措施,那么以Func为主,没有就应用暗许的法子操作redis,那样允许使用者自定义方法扩展了使用者自个儿认为调用token验证的api或然其它合理的点子,那也准保了措施的通用性。

 

 

» 调查问卷系统使用中间件示例

» 调查问卷系统使用中间件示例

上边笔者将选择真实的实例来选择ShenNiuApi.SDK中的中间件方法,那里例子是在自作者调研问卷系统中哪些接纳;首先通过nuget下载 Install-Package
ShenNiuApi.SDK 最新的sdk,然后供给在做登录验证的Filter中要么延续Controller的父类中(笔者这边是后世)添加如下代码:

882828九五至尊手机版,上边小编将运用真实的实例来行使ShenNiuApi.SDK中的中间件方法,这里例子是在本人调查问卷系统中怎么样行使;首先通过nuget下载 Install-Package
ShenNiuApi.SDK 最新的sdk,然后需要在做登录验证的Filter中仍旧再三再四Controller的父类中(小编那里是后人)添加如下代码:

 1 public class BaseController : Controller
 2     {
 3 
 4         protected StageModel.MoUserData _userData;
 5 
 6         protected override void OnActionExecuting(ActionExecutingContext filterContext)
 7         {
 8 
 9             #region 采用ShenNiuApiClient的SsoClient中间件
10 
11             ShenNiuApi.SDK.ShenNiuApiClient client = new ShenNiuApi.SDK.ShenNiuApiClient();
12 
13             var ssoLogin="http://www.lovexins.com:8081/User/Login";
14             var redirectUrl = filterContext.HttpContext.Request.Url.AbsoluteUri;
15             var token = string.Empty;
16             var returnUrl = client.SsoMiddleWareClient<StageModel.MoUserData>(System.Web.HttpContext.Current, ssoLogin, redirectUrl, ref this._userData, ref token);
17             if (string.IsNullOrWhiteSpace(token) )
18             {
19                 filterContext.Result = new RedirectResult(returnUrl);
20                 return;
21             }
22             #endregion
23         }
24 
25         protected void ShowMsg(string msg)
26         {
27 
28             ModelState.AddModelError(string.Empty, msg);
29         }
30     }
 1 public class BaseController : Controller
 2     {
 3 
 4         protected StageModel.MoUserData _userData;
 5 
 6         protected override void OnActionExecuting(ActionExecutingContext filterContext)
 7         {
 8 
 9             #region 采用ShenNiuApiClient的SsoClient中间件
10 
11             ShenNiuApi.SDK.ShenNiuApiClient client = new ShenNiuApi.SDK.ShenNiuApiClient();
12 
13             var ssoLogin="http://www.lovexins.com:8081/User/Login";
14             var redirectUrl = filterContext.HttpContext.Request.Url.AbsoluteUri;
15             var token = string.Empty;
16             var returnUrl = client.SsoMiddleWareClient<StageModel.MoUserData>(System.Web.HttpContext.Current, ssoLogin, redirectUrl, ref this._userData, ref token);
17             if (string.IsNullOrWhiteSpace(token) )
18             {
19                 filterContext.Result = new RedirectResult(returnUrl);
20                 return;
21             }
22             #endregion
23         }
24 
25         protected void ShowMsg(string msg)
26         {
27 
28             ModelState.AddModelError(string.Empty, msg);
29         }
30     }

只需求一句 client.SsoMiddleWareClient<StageModel.MoUserData>(System.Web.HttpContext.Current,
ssoLogin, redirectUrl, ref this._userData, ref
token); 即可实现问卷系统单点登录的验证和收获登录用户的音信,种种解析和设置sid的cookie新闻都早就在中间件方法中完结了,是还是不是相当的大减弱了您的编码量;为了比较上边作者直接贴出没有动用SsoMiddleWareClient方法时候的代码量:

只须求一句 client.SsoMiddleWareClient<StageModel.MoUserData>(System.Web.HttpContext.Current,
ssoLogin, redirectUrl, ref this._userData, ref
token); 即可完毕问卷系统单点登录的注解和获取登录用户的新闻,种种解析和装置sid的cookie音信都曾经在中间件方法中成功了,是否宏大裁减了您的编码量;为了相比上边小编直接贴出没有运用SsoMiddleWareClient方法时候的代码量:

882828九五至尊手机版 3882828九五至尊手机版 4

882828九五至尊手机版 5882828九五至尊手机版 6

 1 protected override void OnActionExecuting(ActionExecutingContext filterContext)
 2         {
 3 
 4 
 5             var returnUrl = filterContext.HttpContext.Request.Url.AbsoluteUri;
 6             returnUrl = HttpUtility.UrlEncode(returnUrl);
 7             // var result = new RedirectResult(string.Format("http://www.lovexins.com:8081/User/Login?returnUrl={0}", returnUrl));
 8             var result = new RedirectResult(string.Format("http://172.16.9.6:4040/User/Login?returnUrl={0}", returnUrl));
 9             var key = "Sid";
10             var timeOut = 30;
11             try
12             {
13                 var cookie = filterContext.HttpContext.Request.Cookies.Get(key);
14                 var token = filterContext.HttpContext.Request.Params["token"];
15                 token = string.IsNullOrWhiteSpace(token) ? (cookie == null ? "" : cookie.Value) : token;
16                 if (string.IsNullOrWhiteSpace(token))
17                 {
18                     filterContext.Result = result;
19                     return;
20                 }
21 
22                 this._userData = CacheRepository.Current(CacheType.RedisCache).GetCache<StageModel.MoUserData>(token, true);
23                 if (this._userData == null && cookie != null)
24                 {
25                     //清空cookie
26                     cookie.Expires = DateTime.Now.AddDays(-1);
27                     filterContext.HttpContext.Response.SetCookie(cookie);
28                     filterContext.Result = result;
29                     return;
30                 }
31                 else if (this._userData == null)
32                 {
33                     filterContext.Result = result;
34                     return;
35                 }
36 
37                 if (cookie == null)
38                 {
39                     cookie = new HttpCookie(key, token);
40                     cookie.Expires = DateTime.Now.AddMinutes(timeOut);
41                     filterContext.HttpContext.Response.AppendCookie(cookie);
42                 }
43                 else
44                 {
45                     cookie.Value = token;
46                     //登陆验证都成功后,需要重新设置cookie中的toke失效时间
47                     cookie.Expires = DateTime.Now.AddMinutes(timeOut);
48                     filterContext.HttpContext.Response.SetCookie(cookie);
49                 }
50 
51                 //设置session失效时间
52                 CacheRepository.Current(CacheType.RedisCache).AddExpire(token, timeOut);
53             }
54             catch (Exception ex)
55             {
56                 filterContext.Result = result;
57                 return;
58             }
59         }
 1 protected override void OnActionExecuting(ActionExecutingContext filterContext)
 2         {
 3 
 4 
 5             var returnUrl = filterContext.HttpContext.Request.Url.AbsoluteUri;
 6             returnUrl = HttpUtility.UrlEncode(returnUrl);
 7             // var result = new RedirectResult(string.Format("http://www.lovexins.com:8081/User/Login?returnUrl={0}", returnUrl));
 8             var result = new RedirectResult(string.Format("http://172.16.9.6:4040/User/Login?returnUrl={0}", returnUrl));
 9             var key = "Sid";
10             var timeOut = 30;
11             try
12             {
13                 var cookie = filterContext.HttpContext.Request.Cookies.Get(key);
14                 var token = filterContext.HttpContext.Request.Params["token"];
15                 token = string.IsNullOrWhiteSpace(token) ? (cookie == null ? "" : cookie.Value) : token;
16                 if (string.IsNullOrWhiteSpace(token))
17                 {
18                     filterContext.Result = result;
19                     return;
20                 }
21 
22                 this._userData = CacheRepository.Current(CacheType.RedisCache).GetCache<StageModel.MoUserData>(token, true);
23                 if (this._userData == null && cookie != null)
24                 {
25                     //清空cookie
26                     cookie.Expires = DateTime.Now.AddDays(-1);
27                     filterContext.HttpContext.Response.SetCookie(cookie);
28                     filterContext.Result = result;
29                     return;
30                 }
31                 else if (this._userData == null)
32                 {
33                     filterContext.Result = result;
34                     return;
35                 }
36 
37                 if (cookie == null)
38                 {
39                     cookie = new HttpCookie(key, token);
40                     cookie.Expires = DateTime.Now.AddMinutes(timeOut);
41                     filterContext.HttpContext.Response.AppendCookie(cookie);
42                 }
43                 else
44                 {
45                     cookie.Value = token;
46                     //登陆验证都成功后,需要重新设置cookie中的toke失效时间
47                     cookie.Expires = DateTime.Now.AddMinutes(timeOut);
48                     filterContext.HttpContext.Response.SetCookie(cookie);
49                 }
50 
51                 //设置session失效时间
52                 CacheRepository.Current(CacheType.RedisCache).AddExpire(token, timeOut);
53             }
54             catch (Exception ex)
55             {
56                 filterContext.Result = result;
57                 return;
58             }
59         }

View Code

View Code

从代码量看前者不难多了,有人会说了你那不正是弄了一个方式而已嘛,说怎么代码量少了哈哈;那只可以说一般咋们哎使用第①方的插件恐怕类库,那样石破惊天收缩了咋们工作量和升级了付出速度的便宜,有了ShenNiuApi.SDK你还必要担心如何啊;可是切磋之中的具体步骤,逻辑代码小编嘶吼相当的赞成的;

从代码量看前者不难多了,有人会说了您那不就是弄了三个主意而已嘛,说哪些代码量少了哈哈;那不得不说日常咋们哎使用第二方的插件可能类库,那样庞大缩小了咋们工作量和升高了费用进程的便宜,有了ShenNiuApi.SDK您还要求操心怎样呢;不过探讨之中的具体步骤,逻辑代码作者嘶吼卓殊扶助的;

有了在调查问卷的自定义Controller父类后,咋们还必要有四个登录的地点,那里自身新创立的连串Stage.Web,在其登录get请求的Action中加进了之类代码:

有了在调查问卷的自定义Controller父类后,咋们还须要有二个记名的地点,那里自身新成立的花色Stage.Web,在其登录get请求的Action中加进了之类代码:

 1    #region 采用ShenNiuApiClient的SsoClient中间件
 2 
 3             ShenNiuApi.SDK.ShenNiuApiClient client = new ShenNiuApi.SDK.ShenNiuApiClient();
 4             var ssoLogin = loginUrl;
 5             var redirectUrl = context.Request.Path;
 6 
 7             var token = string.Empty;
 8             t = default(T);
 9             var returnUrl = client.SsoMiddleWareClient<T>(System.Web.HttpContext.Current, ssoLogin, redirectUrl, ref t, ref token, sidName: UserLoginExtend.CookieName);
10             if (string.IsNullOrWhiteSpace(token))
11             {
12                 return new RedirectResult(returnUrl);
13             }
14             return null;
15             #endregion
 1    #region 采用ShenNiuApiClient的SsoClient中间件
 2 
 3             ShenNiuApi.SDK.ShenNiuApiClient client = new ShenNiuApi.SDK.ShenNiuApiClient();
 4             var ssoLogin = loginUrl;
 5             var redirectUrl = context.Request.Path;
 6 
 7             var token = string.Empty;
 8             t = default(T);
 9             var returnUrl = client.SsoMiddleWareClient<T>(System.Web.HttpContext.Current, ssoLogin, redirectUrl, ref t, ref token, sidName: UserLoginExtend.CookieName);
10             if (string.IsNullOrWhiteSpace(token))
11             {
12                 return new RedirectResult(returnUrl);
13             }
14             return null;
15             #endregion

一贯通过中间件提供的 SsoMiddleWareClient 方法取得登录的token并表达是还是不是曾经登陆过,即使登录过了第3手通过 return new
RedirectResult(returnUrl); 重定向到returnUrl的地址中去;假如没有那么进入登录界面,录入账号新闻后:

一向通过中间件提供的 SsoMiddleWareClient 方法获得登录的token并证实是还是不是业已登陆过,尽管登录过了直白通过 return new
RedirectResult(returnUrl); 重定向到returnUrl的地址中去;假诺没有那么进入登录界面,录入账号音信后:

882828九五至尊手机版 7

882828九五至尊手机版 8

付出登录,进入咋们post的Action中进过数据库对账号匹配成功了,然后径直调用中间件方法来存款和储蓄session并提供唯一的token值,再进行重定向跳转:

交由登录,进入咋们post的Action中进过数据库对账号匹配成功了,然后径直调用中间件方法来储存session并提供唯一的token值,再展开重定向跳转:

 1  #region 采用ShenNiuApiClient的SsoServer中间件
 2 
 3                     ShenNiuApi.SDK.ShenNiuApiClient client = new ShenNiuApi.SDK.ShenNiuApiClient();
 4 
 5                     var timeOut = 60; //分钟
 6                     var token = string.Empty;
 7                     var redirectUrl = client.SsoMiddleWareServer<StageModel.MoUserData>(userData, returnUrl, ref token, timeOut: timeOut);
 8                     sbLog.AppendFormat("redirectUrl:{0},token:{1},", redirectUrl, token);
 9                     if (string.IsNullOrWhiteSpace(token) || string.IsNullOrWhiteSpace(redirectUrl))
10                     {
11                         //登陆失败
12                         sbLog.Append("登陆失败,");
13                     }
14                     else
15                     {
16                         //写入Sso统一登陆站点的sid到cookie
17                         var cookie = new HttpCookie(UserLoginExtend.CookieName, token);
18                         cookie.Expires = DateTime.Now.AddMinutes(timeOut);
19                         cookie.Domain = Request.Url.Host;
20                         HttpContext.Response.AppendCookie(cookie);
21                     }
22                     var isAddLog = await StageClass._WrigLogAsync(sbLog.ToString());
23                     return new RedirectResult(string.Format("{0}", redirectUrl));
24                     #endregion
 1  #region 采用ShenNiuApiClient的SsoServer中间件
 2 
 3                     ShenNiuApi.SDK.ShenNiuApiClient client = new ShenNiuApi.SDK.ShenNiuApiClient();
 4 
 5                     var timeOut = 60; //分钟
 6                     var token = string.Empty;
 7                     var redirectUrl = client.SsoMiddleWareServer<StageModel.MoUserData>(userData, returnUrl, ref token, timeOut: timeOut);
 8                     sbLog.AppendFormat("redirectUrl:{0},token:{1},", redirectUrl, token);
 9                     if (string.IsNullOrWhiteSpace(token) || string.IsNullOrWhiteSpace(redirectUrl))
10                     {
11                         //登陆失败
12                         sbLog.Append("登陆失败,");
13                     }
14                     else
15                     {
16                         //写入Sso统一登陆站点的sid到cookie
17                         var cookie = new HttpCookie(UserLoginExtend.CookieName, token);
18                         cookie.Expires = DateTime.Now.AddMinutes(timeOut);
19                         cookie.Domain = Request.Url.Host;
20                         HttpContext.Response.AppendCookie(cookie);
21                     }
22                     var isAddLog = await StageClass._WrigLogAsync(sbLog.ToString());
23                     return new RedirectResult(string.Format("{0}", redirectUrl));
24                     #endregion

到此出sso的代码基本做到了就好像此不难,可是那里暗许使用的是本身嵌入的redis服务来囤积session音讯的,所以还索要布置3个redis相关账号密码等的节点,那里只要求您在 C:\Conf\ShenNiuApi.xml 磁盘上边增添如下名称的xml文件,文件内容也简要:

到此出sso的代码基本做到了就像此不难,不过那里暗中同意使用的是自个儿嵌入的redis服务来囤积session消息的,所以还亟需安顿三个redis相关账号密码等的节点,那里只须求您在 C:\Conf\ShenNiuApi.xml 磁盘上边扩展如下名称的xml文件,文件内容也简要:

 1 <ShenNiuApi>
 2     <RedisCache>
 3         <!--读写权限服务地址,多个使用'|'隔开(格式如:pwd@ip:port)-->
 4         <UserName>shenniubuxing3@111.111.111.152:1111</UserName>
 5         <!--只读权限服务地址,多个使用'|'隔开-->
 6         <UserPwd>shenniubuxing3@111.111.111.152:1111|shenniubuxing3@127.0.0.1:6377</UserPwd>
 7         <ApiUrl></ApiUrl>
 8         <ApiKey></ApiKey>
 9     </RedisCache>
10 </ShenNiuApi>
 1 <ShenNiuApi>
 2     <RedisCache>
 3         <!--读写权限服务地址,多个使用'|'隔开(格式如:pwd@ip:port)-->
 4         <UserName>shenniubuxing3@111.111.111.152:1111</UserName>
 5         <!--只读权限服务地址,多个使用'|'隔开-->
 6         <UserPwd>shenniubuxing3@111.111.111.152:1111|shenniubuxing3@127.0.0.1:6377</UserPwd>
 7         <ApiUrl></ApiUrl>
 8         <ApiKey></ApiKey>
 9     </RedisCache>
10 </ShenNiuApi>

把内容之中的redis账号,密码,端口,地址改成您自个儿的就行了;因为是在C盘中所以你服务器的别的站点也能够访问,假设你暗中认可使用redis的法子存款和储蓄session,那么只须要依据地方步骤就能急迅的搭建贰个单点登录架构;那里自个儿提供下调查问卷使用单点登录测试的地址:www.lovexins.com:1001/Subject
测试账号:shenniu003
密码:123123,注意登录界面包车型地铁域名和问卷调查的域名一样,只是端口不雷同而已,若是您要看效果能够在浏览器F12,然后如图操作:

把内容之中的redis账号,密码,端口,地址改成您自个儿的就行了;因为是在C盘中所以你服务器的其余站点也能够访问,假设你暗中同意使用redis的格局存款和储蓄session,那么只供给依据上边步骤就能急速的搭建三个单点登录框架结构;那里小编提供下调查问卷使用单点登录测试的地点:www.lovexins.com:1001/Subject
测试账号:shenniu003
密码:123123,注意登录界面的域名和问卷调查的域名一样,只是端口不等同而已,若是你要看效率能够在浏览器F12,然后如图操作:

882828九五至尊手机版 9

882828九五至尊手机版 10

能够看出这一个sid正是地方栏中的token值,那正是咋们定义的sessionId拉,您不想尝试吧。

可见看到那几个sid便是地方栏中的token值,那正是咋们定义的sessionId拉,您不想试试啊。

 

 

» 推广调查问卷系统

» 推广调查问卷系统

考察问卷笔者想许多商家都会用到,我们一般都会本身做一套,小编那边要为我们推荐的是神牛问卷,具体怎么试用呢,能够登录地址http://www.lovexins.com:8081/User/Login 账号:shenniiu003
密码:123123,进入系统后直接点击“问卷管理”=>”调查问卷”,在此地您就足以添加你想调查的问卷新闻和选拔:

调查研讨问卷作者想许多供销合作社都会用到,我们一般都会友善做一套,作者那边要为大家推荐的是神牛问卷,具体怎么试用呢,能够登录地址http://www.lovexins.com:8081/User/Login 账号:shenniiu003
密码:123123,进入系统后一直点击“问卷管理”=>”调查问卷”,在此地你就足以添加你想调查的问卷新闻和选拔:

882828九五至尊手机版 11

882828九五至尊手机版 12

假设你添加成就问卷新闻后,能够一贯点击“阅览”查看您的问卷呈现内容和办法(帮助移入手提式有线电话机浏览访问),那也是填充调查问卷的人见到的界面,方今支持的标题类型有(单选,多选,文本输入),测试地方:http://www.lovexins.com:1001/shenniu003/wenjuan7,地址中的shenniu003是依照账号来浮现的,借使您是有些公司的hr也许老董那里地址栏能够直接登记成您集团的拼音名称大概汉字(是或不是觉得还是能吧):

若是您添加成就问卷音信后,能够直接点击“阅览”查看您的问卷展现内容和艺术(支持移入手提式有线电话机浏览访问),那也是填充调查问卷的人看出的界面,近年来帮助的题材类型有(单选,多选,文本输入),测试地方:http://www.lovexins.com:1001/shenniu003/wenjuan7,地址中的shenniu003是遵照账号来体现的,借使您是有些集团的hr恐怕COO那里地址栏能够一直登记成您公司的拼音名称可能汉字(是否觉得还能吧):

882828九五至尊手机版 13

882828九五至尊手机版 14

关键点来了,有了填写的用户咋们供给分析并做总计,这几个时候只供给您点击问卷列表中的”统计“,就能看到如下名指标图片:

关键点来了,有了填写的用户咋们供给分析并做总括,这几个时候只必要你点击问卷列表中的”统计“,就能观望如下名指标图样:

882828九五至尊手机版 15

882828九五至尊手机版 16

您能够点击某八个题材选取对应的“红色”条,直接进入用户挑选的剖析报表:

你能够点击某三个题材选用对应的“红色”条,直接进去用户选拔的解析报表:

882828九五至尊手机版 17

882828九五至尊手机版 18

看起来效果依然比较不易的啊,关键有数量总计给业主照旧别的朋友看的时候,令人觉得“高大上”,那是挑选样式的总括图,那么一旦是用户填写类的计算吗,是之类那样的列表:

看起来效果照旧相比科学的呢,关键有多少总计给老总娘依旧别的朋友看的时候,令人倍感“高大上”,那是选用样式的总结图,那么一旦是用户填写类的总括吗,是之类那样的列表:

882828九五至尊手机版 19

882828九五至尊手机版 20

特点:

特点:

  1. 带有单选,多选,用户填写类的标题类型

  2. 单点登录架构,能高效嵌入到此外系统中

  3. 手提式无线电话机web也能访问考察问问卷,问答难题

  4. 详见的表格总计

  5. 正式的掩护人士哈哈

  1. 饱含单选,多选,用户填写类的标题类型

  2. 单点登录架构,能急速嵌入到此外系统中

  3. 手提式有线电电话机web也能访问考察问问卷,问答难题

  4. 详见的报表总计

  5. 正式的掩护人士哈哈

表明:最终要说的是此调查商讨问卷系统是为了方便须要用到此效率的情人和商店,假若你觉得还足以想发一几个问卷调查内容,能够沟通自己并让自身给你单独分配二个管理者账号,当然如若您是某些公司首领也想长久使用此调查系统能够调换邮箱:841202396@qq.com,随便您发多少问卷只要符合法定剧情;

申明:最终要说的是此调查探究问卷系统是为着便利需求用到此效用的爱人和供销合作社,若是您觉得还足以想发一四个问卷调查内容,可以联系本身并让笔者给您单独分配多个领导账号,当然假诺你是有个别公司首领也想长久使用此调查研商系统可以联系邮箱:841202396@qq.com,随便您发多少问卷只要顺应法定情节;

 

 

补充:

补充:

2017.03.06

2017.03.06

应多少个博友的急需,那里发送交检验察问卷源码:ShenNiu.Question(问卷调查-源码包)

应两个博友的急需,那里发送交检验察问卷源码:ShenNiu.Question(问卷调查-源码包)

相关文章

Your Comments

近期评论

    功能


    网站地图xml地图