.net mvc通过ucenter和 discuz的整合,nopcommerce ucenter 插件的方式实现
discuz无疑是目前市面上最好的论坛之一,对于大多数公司来说,用discuz搭建一个论坛肯定是最节约成本的选择,然而我们的会员想要和discuz进行整合的话,只有两种荀泽,一种直接选用discuz的数据库的用户表作为自己系统的用户表(这种不现实,我如果是mssql,或者是oricle怎么办呢?),第二种就是使用discuz为了解决大家这中需求而提出的ucenter技术,目前小米论坛就是采用的这种技术,下面我就为大家介绍一下.net下使用ucenter的注意细节。
首先引入的第三方已经开发好的ucenter sdk,其原理在其主页文章http://www.dozer.cc/2011/01/ucenter-api-in-depth-1st/
下面我介绍在nopcommerce中使用ucenter的步奏
第一步:你要得到和UCenter进行通信的类库,作者的主页有原始类库http://www.dozer.cc/2011/05/ucenter-api-for-net-on-codeplex/,是asp.net 4.0版的。
第二步,你在你的asp.net项目中引用该类库,引用类库的时候,Browse到dll就行,没有必要把整个项目原代码添加到你的解决方案中。
这个类库有两个重点的文件夹需要注意:DS.Web.UCenter.Api与DS.Web.UCenter.Client,
其中DS.Web.UCenter.Api用于响应由UCenter中心发出的通知消息;
然后DS.Web.UCenter.Client用于本地向UCenter发送消息;
第三步,你需要一个响应通知的页面了,这里为了尊重原作者,我们相应地址的倒数第二层一定要是api,所以我们可以注册一个路由,让该路由的倒数第二层为api
protected override void RegisterPluginRoutes(RouteCollection routes) { RegisterPluginRoutesAdmin(routes, "UCenter"); routes.MapRoute("apiuc", "api/uc", new { controller = "UCenterOpenApi", action = "OpenApi" }, new[] { Constants.ControllersNamespace }); }
这样子我们就可以通过 http://localhost:305/api/uc 访问到指定控制器下的方法了,
接下来我们就要写UCenterOpenApiController控制器下的OpenApi方法了,代码如下
public UCenterOpenApiController(UcApiService ucApiService, HttpSessionStateBase httpSession, UCenterSetting uCenterSetting)
{ this._ucApiService = ucApiService; this._httpSession = httpSession; this._uCenterSetting = uCenterSetting; } public ActionResult OpenApi() { _ucApiService.ProcessRequest(System.Web.HttpContext.Current); return Content(""); }
这段代码的意思就是依赖注入 UcApiService服务,调用UcApiService的PR方法,我想聪明的你一定猜到UcApiService一定是继承了.net中的IHttpHandler接口才能条用PR方法的对吧,
没错,你猜的很正确,下面看一下UcApiService的代码
public class UcApiService : UcApiBase { #region Fields /// <summary> /// 认证服务 /// </summary> private IAuthenticationService _authenticationService; //....字段略 #region 同步登陆 /// <summary> /// 同步登陆 /// </summary> /// <param name="uid">uid</param> /// <returns>ApiReturn</returns> public override ApiReturn SynLogin(int uid) { //IUcClient client = new UcClient(); var user = _uclient.UserInfo(uid); if (user != null && user.Success) { Customer customer = GetNopCustomer(user); if (customer != null) _authenticationService.SignIn(customer, true); else { customer = new Customer { CustomerGuid = Guid.NewGuid(), Email = user.Mail, Username = user.UserName, Password = new Random().Next(100000).ToString(), VendorId = 0, Active = true, CreatedOnUtc = DateTime.UtcNow, LastActivityDateUtc = DateTime.UtcNow, }; _customerService.InsertCustomer(customer); //角色 _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.FirstName, user.UserName); //add to 'Registered' role var registeredRole = _customerService.GetCustomerRoleBySystemName(SystemCustomerRoleNames.Registered); if (registeredRole == null) throw new NopException("'Registered' role could not be loaded"); customer.CustomerRoles.Add(registeredRole); //Add reward points for customer registration (if enabled) if (_rewardPointsSettings.Enabled && _rewardPointsSettings.PointsForRegistration > 0) customer.AddRewardPointsHistoryEntry(_rewardPointsSettings.PointsForRegistration, _localizationService.GetResource("RewardPoints.Message.EarnedForRegistration")); _customerService.UpdateCustomer(customer); _authenticationService.SignIn(customer, true); } return ApiReturn.Success; } else return ApiReturn.Failed; } #endregion //其他的实现略 }
可以看出我们的自定义UcApiService继承了 一开始提到的sdk中的UcApiBase,而UcApiBase实现了IHttpHandler和IRequiresSessionState接口,所以在我们的方法中可以直接调用
UcApiService的PR方法。UcApiBase鸡肋中还有好多的方法等待实现,我就不一一列出了,具体可以自己去查看。我们就看这个同步登陆的方法,大体意思就是ucenter下属有其它站点登陆了,我们只要在这个方法中查找的这个用户,并且设置他在本站的状态也为登陆状态,说白了就是授权用户登录本站,只是不需要用户再次的输入账号密码而已。
第四步,上一步是ucenter通知我们有用户从其他站登录了,要求我们这里也登录,那么如果有会员通过我们这个站登录了,我们也是要告诉ucenter的,所以我们这里需要在会员登录成功的情况下,告诉ucenter,这个会员已经成功登录了,你可以让他在其他站也进行登录,具体的实现就是在生成用户凭证的时候调用sdk中IUcClient中的UserLogin方法,如果登录成功,改方法会返回一段js脚本,我们只需要把这段js脚本放到页面上即可。具体代码如下
/// <summary> /// ucenter自定义 登陆认证和 退出认证 实现 /// </summary> public class NopUiUcenterAuthenticationService : INopUiUcenterAuthenticationService { #region Fields /// <summary> /// HttpContextBase /// </summary> private HttpContextBase _httpContext; //private readonly ICustomerService _customerService; /// <summary> /// CustomerSettings /// </summary> private readonly CustomerSettings _customerSettings; /// <summary> /// UCenterSetting /// </summary> private UCenterSetting _uCenterSetting; /// <summary> /// IUcClient /// </summary> private IUcClient _uclient; /// <summary> /// 日志服务 /// </summary> private ILogger _logger; // private ICustomerActivityService _customerActivityService; #endregion #region Ctor /// <summary> /// ctor /// </summary> /// <param name="httpContext">HttpContextBase</param> /// <param name="customerSettings">CustomerSettings</param> /// <param name="_uCenterSetting">UCenterSetting</param> /// <param name="_uclient">IUcClient</param> /// <param name="_logger">日志服务</param> public NopUiUcenterAuthenticationService(HttpContextBase httpContext, // ICustomerService customerService, CustomerSettings customerSettings, UCenterSetting _uCenterSetting, IUcClient _uclient, ILogger _logger // ICustomerActivityService _customerActivityService ) { this._httpContext = httpContext; // this._customerService = customerService; this._customerSettings = customerSettings; this._uCenterSetting = _uCenterSetting; this._uclient = _uclient; this._logger = _logger; //this._customerActivityService = _customerActivityService; } #endregion #region Methods /// <summary> /// 认证 /// </summary> /// <param name="customer">Customer</param> public void SignIn(Customer customer) { if (!_uCenterSetting.AvailableUcenter || _httpContext.Request.Form["Password"] == null) return; //同步登陆 try { string Password = _httpContext.Request.Form["Password"].ToString().Trim(); //IUcClient client = new UcClient(); UcUserLogin user = null; if (_customerSettings.UsernamesEnabled) user = _uclient.UserLogin(customer.Username, Password);//登陆 else user = _uclient.UserLogin(customer.Email, Password, LoginMethod.Mail);//登陆 if (user != null && user.Success)//判断是否登陆成功 { switch (user.Result) { case LoginResult.Success: //保存到会话中 // _customerActivityService.InsertActivity("uCenter", "客户从本地登陆,并且成功同步到uCenter中", customer); _httpContext.Session[Constants.UcenterLoginSessionKey] = _uclient.UserSynlogin(user.Uid); break; case LoginResult.NotExist: //用户不存在 那么就注册到ucenter吧 //_logger var registerResult = _uclient.UserRegister(customer.Username, Password, customer.Email); if (registerResult.Result == RegisterResult.Success) { // _customerActivityService.InsertActivity("uCenter", "客户在本地存在,但是在ucenter中不存在,此处注册此用户到uCenter中成功", customer); _httpContext.Session[Constants.UcenterLoginSessionKey] = _uclient.UserSynlogin(registerResult.Uid); } else _logger.Error(registerResult.Result.ToString() + "---同步登陆到uCenter时异常,描述:本地已经存在的用户但是ucenter中没有,试图向ucenter中注册此用户的时候,注册失败", null, customer); break; case LoginResult.PassWordError: //密码不对,那就把用户中心的密码改成和本商城中一样的呗 var s = _uclient.UserEdit(customer.Username, null, Password, null, true); if (s.Result == UserEditResult.Success) { //_customerActivityService.InsertActivity("uCenter", "客户在本地和ucenter中的密码不一样,但是以本商城的密码为主,所以到ucenter中修改此用户的登陆密码,成功", customer); _httpContext.Session[Constants.UcenterLoginSessionKey] = _uclient.UserSynlogin(user.Uid); } break; case LoginResult.QuestionError: break; default: break; } } else { _logger.Error("ucteter同步异常,请查看ucenter配置页的配置是否正确", null, customer); } } catch (Exception ex) { _logger.Error("ucteter同步异常", ex, customer); } } /// <summary> /// 取消认证 /// </summary> public void SignOut() { if (_uCenterSetting.AvailableUcenter) _httpContext.Session[Constants.UcenterLoginOutSessionKey] = _uclient.UserSynLogout(); } #endregion }
我们把返回的js脚本方法 会话中,再跳转的时候把这段脚本打到页面上,再删除这段会话即可。
第五步,这步是写配置信息,因为原始类库的配置信息都写到它自己的App.config中去了,但是我这里把作者从配置文件读出配置的方式改成从数据库读取了,配置信息如下图所示
第六步,检查本地服务器和UCenter服务器是否能正常通信
第七步,去UCenter添加此应用,具体怎么填的图片如下:
填写完以后到列表页面看到如下所示,则表示通信成功
现在我们就可以测试是否可以正常同步了。
打开discuz登录一个用户后,再到我们商城中会发现,改用户已经登录了商城,就算该用户在我们商城不存在,我们也会在同步的时候自动注册该用户。
到此 基本上从三美到 discuz 的同步基本完成。
但是,我不知道discuz是故意的还是无意的,这其中一共有三个问题
- 我从discuz登录用户,却无法同步 到三美商城。
- 从三美商城注册的用户,无法即时同步到discuz,需要在discuz登录一下,以后就可以同步了,及需要激活!
- 从discuz注册的用户,无法即时同步 到三美商城。
要想解决这三个问题,都得要从discuz入手,首先解决第一个问题:
打开discuz安装路径下 \bbs\uc_client\data\cache\apps.php:
你会发现只有discuz x 本身应用,所以就不会同步登陆,我也不知道什么原因造成。
我们把三美的应用也加进去即可解决从discuz登录不同步到三美商城的问题
接着解决第二个问题:
第二个问题产生的主要原因是,我们注册的用户,同步登陆的时候,discuz并没有把这个用户添加到他的用户表里面,而是用户激活后才添加到他自己的用户表,找到原因后就很好解决了,
我们只需要在用户同步登陆的时候把用户添加到用户表中即可,
修改文件:/api/uc.php
function synlogin($get, $post) { global $_G; if(!API_SYNLOGIN) { return API_RETURN_FORBIDDEN; } header('P3P: CP="CURa ADMa DEVa PSAo PSDo OUR BUS UNI PUR INT DEM STA PRE COM NAV OTC NOI DSP COR"'); $cookietime = 31536000; $uid = intval($get['uid']); if(($member = getuserbyuid($uid, 1))) { dsetcookie('auth', authcode("$member[password]\t$member[uid]", 'ENCODE'), $cookietime); }else{ $query = DB::query("SELECT email FROM ".DB::table('ucenter_members')." WHERE uid='$uid'"); if($a = DB::fetch($query)){ $email = $a['email']; }else{ $email = $get['email']; } $username = $get['username']; $password = md5(time().rand(100000, 999999)); //$email = $get['email']; $ip = $_SERVER['REMOTE_ADDR']; $time = time(); $userdata = array( 'uid' => $uid, 'username' => $username, 'password' => $password, 'email' => $email, 'adminid' => 0, 'groupid' => 10, 'regdate' => $time, 'credits' => 0, 'timeoffset' => 9999 ); DB::insert('common_member', $userdata); $status_data = array( 'uid' => $uid, 'regip' => $ip, 'lastip' => $ip, 'lastvisit' => $time, 'lastactivity' => $time, 'lastpost' => 0, 'lastsendmail' => 0, ); DB::insert('common_member_status', $status_data); DB::insert('common_member_profile', array('uid' => $uid)); DB::insert('common_member_field_forum', array('uid' => $uid)); DB::insert('common_member_field_home', array('uid' => $uid)); DB::insert('common_member_count', array('uid' => $uid)); if(($member = getuserbyuid($uid, 1))) { dsetcookie('auth', authcode("$member[password]\t$member[uid]", 'ENCODE'), $cookietime); } } }
保存上述代码即可解决第二个问题
下面解决最后一个问题,这个问题其实本质和第二个很相似,只需要在用户注册的时候调用一下登陆方法即可。