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().ToString(),
VendorId = ,
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 > )
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);
}
}
}
保存上述代码即可解决第二个问题
下面解决最后一个问题,这个问题其实本质和第二个很相似,只需要在用户注册的时候调用一下登陆方法即可。