深入研究 UCenter API

UCenter 通讯基本原理

UCenter和各个子站的通讯,主要就是通过 POST 的方式调用而已,没有什么技术含量。

ucenterapi

表单参数都是经过 Base64 算法,加一个通信密钥进行加密和解密的。

返回的数据是单个参数(例如:0或者1),也可能是xml序列化后的数据。

例如一个请求:code=e145fscn314BSKnwxBvqLaQe2yrHJAnKO1M%2B8C4cAKQAtRRQfEqTh8mg665UVJPyrJIrPhDNnEM

解密后:action=test&time=1295631663

返回:1

以上是一个测试是否通讯成功的请求,上面是表单参数,解密的明文如上,返回1代表通讯成功!

而其中的难点就在于,Discuz 并没有公开所有的 API!而仅仅是提供了一个 php 版的函数库,帮你写好了这些通讯函数。

所以 php 的网站做起来很轻松,而别的网站就痛苦了。

这里的痛苦包括加密和解密函数、序列化和反序列化、还有期中各个参数的名称和格式…

怎么解决?只能翻阅 php 版本的源代码,把它的 API 一条条看过去,然后用自己需要用的语言重写一遍。

 

 

UCenter 多点登录的原理及过程

通讯原理就这么点,很简单,子站想联系 UCenter,就调用一个地址,传递一些参数就行了,反之亦然。

最难的其实是多点登录的过程。

因为一般的请求,比如在子站请求登录,调用一下,然后返回数据,就完成了;而多点登录没那么简单。

理解原理后才能知道应该怎么用多点登录。

 

先看一下多点登录的过程吧:

login

子站调用封装好的登录函数(参数:帐号、密码),返回登录信息(包括:uid、用户名、E-mail)

子站调用同步登录函数(参数:uid),返回一段 javascript 代码

在子站前端页面执行这段 javascript 代码

该代码通知其他子站,调用它们的同步登录函数

 

解惑:

刚看完这个,我也很疑惑,为什么要这样?那我反问下,你想怎么样?

1、子站调用同步登录函数,UCenter 通知子站同步登录?

2、子站调用同步登录函数,得到别的子站的同步登录 API,直接调用?

 

为什么上面两个不行?什么叫同步登录?

那就是:用户的电脑需要在每个子站下保存了 Session,或者在用户电脑上保存了该每个子站的 Cookie

 

那前面两个方案:

方案一:在 UCenter 服务器保存了 Cookie,所有子站服务器上的 Session 记录记录的是 UCenter 服务器的,而不是用户的

方案二:同理可得,Cookie 保存在了一台子站的服务器上,Session 记录的是那个子站服务器的

所以,一定要用户的电脑通知各子站才行,那怎么通知呢?通过 javascript

 

关键:

所以,UCenter 在同步登录的过程中,最关键的就是需要把这个 API 返回的 javascript 代码在前端运行

可以这样写:

using DS.Web.UCenterAPI.UCClient;

var client = new UCClient();
var ucLoginReturn =  client.UC_User_Login("admin", "admin");
if(ucLoginReturn.Success)
{
    var js = client.UC_User_Synlogin(ucLoginReturn.User.Uid);
    Response.Write(js);
}

 

这样就可以实现同步登录了,我们来看一下 UC_User_Synlogin 函数返回的 js 代码吧:

login_js

 

 

AuthCode函数

这个函数是什么?传说这是康盛公司对 php 发展做出的一个极大的贡献…

该函数可以实现可逆加密,在 php 中广为流传…

AuthCode

UCenter API 中的加密解密函数,被称为 php 领域的经典之作,也是康盛公司为 php 做的一大贡献

这个函数,可以通过一个 KEY ,生成动态的密文,并可以再通过这个 KEY 来解密

我没有研究过什么加密算法,所以对这个的基础知识也不是很了解,或许在 C# 中会有更强大的算法,但是这个函数在做 UCenter API 的时候是必需的。

也是 UCenter API php 版翻译成 C# 版本中最难的一个部分。

 

PHP 版详解

// $string: 明文 或 密文
// $operation:DECODE表示解密,其它表示加密
// $key: 密匙
// $expiry:密文有效期
//字符串解密加密
function authcode($string, $operation = 'DECODE', $key = '', $expiry = 0) {
     // 动态密匙长度,相同的明文会生成不同密文就是依靠动态密匙
    $ckey_length = 4;   // 随机密钥长度 取值 0-32;
                // 加入随机密钥,可以令密文无任何规律,即便是原文和密钥完全相同,加密结果也会每次不同,增大破解难度。
                // 取值越大,密文变动规律越大,密文变化 = 16 的 $ckey_length 次方
                // 当此值为 0 时,则不产生随机密钥
    // 密匙
    $key = md5($key ? $key : UC_KEY);
    // 密匙a会参与加解密
    $keya = md5(substr($key, 0, 16));
    // 密匙b会用来做数据完整性验证
    $keyb = md5(substr($key, 16, 16));
    // 密匙c用于变化生成的密文
    $keyc = $ckey_length ? ($operation == 'DECODE' ? substr($string, 0, $ckey_length): substr(md5(microtime()), -$ckey_length)) : '';
    // 参与运算的密匙
    $cryptkey = $keya.md5($keya.$keyc);
    $key_length = strlen($cryptkey);   

    // 明文,前10位用来保存时间戳,解密时验证数据有效性,10到26位用来保存$keyb(密匙b),解密时会通过这个密匙验证数据完整性
    // 如果是解码的话,会从第$ckey_length位开始,因为密文前$ckey_length位保存 动态密匙,以保证解密正确
    $string = $operation == 'DECODE' ? base64_decode(substr($string, $ckey_length)) : sprintf('%010d', $expiry ? $expiry + time() : 0).substr(md5($string.$keyb), 0, 16).$string;
    $string_length = strlen($string);   

    $result = '';
    $box = range(0, 255);   

    $rndkey = array();
    // 产生密匙簿
    for($i = 0; $i <= 255; $i++) {
        $rndkey[$i] = ord($cryptkey[$i % $key_length]);
     }
     // 用固定的算法,打乱密匙簿,增加随机性,好像很复杂,实际上对并不会增加密文的强度
    for($j = $i = 0; $i < 256; $i++) {
        $j = ($j + $box[$i] + $rndkey[$i]) % 256;
        $tmp = $box[$i];
        $box[$i] = $box[$j];
        $box[$j] = $tmp;
     }
    // 核心加解密部分
    for($a = $j = $i = 0; $i < $string_length; $i++) {
        $a = ($a + 1) % 256;
        $j = ($j + $box[$a]) % 256;
        $tmp = $box[$a];
        $box[$a] = $box[$j];
        $box[$j] = $tmp;
        // 从密匙簿得出密匙进行异或,再转成字符
        $result .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % 256]));
     }   

    if($operation == 'DECODE') {
        // 验证数据有效性,请看未加密明文的格式
        if((substr($result, 0, 10) == 0 || substr($result, 0, 10) - time() > 0) && substr($result, 10, 16) == substr(md5(substr($result, 26).$keyb), 0, 16)) {
            return substr($result, 26);
         } else {
            return '';
         }
     } else {
         // 把动态密匙保存在密文里,这也是为什么同样的明文,生产不同密文后能解密的原因
         // 因为加密后的密文可能是一些特殊字符,复制过程可能会丢失,所以用base64编码
        return $keyc.str_replace('=', '', base64_encode($result));
     }
}

 

这份详解不是我写的,网上有很多,找不到原作者了

 

 

C# 版

/// <summary>
/// AuthCode解码&编码
/// </summary>
/// <param name="sourceStr">原始字符串</param>
/// <param name="operation">操作类型</param>
/// <param name="keyStr">API KEY</param>
/// <param name="expiry">过期时间 0代表永不过期</param>
/// <returns></returns>
private static string AuthCode(string sourceStr, AuthCodeMethod operation, string keyStr, int expiry = 0)
{
    var ckeyLength = 4;
    var source = Encode.GetBytes(sourceStr);
    var key = Encode.GetBytes(keyStr);

    key = Md5(key);

    var keya = Md5(SubBytes(key, 0, 0x10));
    var keyb = Md5(SubBytes(key, 0x10, 0x10));
    var keyc = (ckeyLength > 0)
                    ? ((operation == AuthCodeMethod.Decode)
                            ? SubBytes(source, 0, ckeyLength)
                            : RandomBytes(ckeyLength))
                    : new byte[0];

    var cryptkey = AddBytes(keya, Md5(AddBytes(keya, keyc)));
    var keyLength = cryptkey.Length;

    if (operation == AuthCodeMethod.Decode)
    {
        while (source.Length % 4 != 0)
        {
            source = AddBytes(source, Encode.GetBytes("="));
        }
        source = Convert.FromBase64String(BytesToString(SubBytes(source, ckeyLength)));
    }
    else
    {
        source =
            AddBytes(
                (expiry != 0
                        ? Encode.GetBytes((expiry + PhpTimeNow()).ToString())
                        : Encode.GetBytes("0000000000")),
                SubBytes(Md5(AddBytes(source, keyb)), 0, 0x10), source);
    }

    var sourceLength = source.Length;

    var box = new int[256];
    for (var k = 0; k < 256; k++)
    {
        box[k] = k;
    }

    var rndkey = new int[256];
    for (var i = 0; i < 256; i++)
    {
        rndkey[i] = cryptkey[i % keyLength];
    }

    for (int j = 0, i = 0; i < 256; i++)
    {
        j = (j + box[i] + rndkey[i]) % 256;
        var tmp = box[i];
        box[i] = box[j];
        box[j] = tmp;
    }

    var result = new byte[sourceLength];
    for (int a = 0, j = 0, i = 0; i < sourceLength; i++)
    {
        a = (a + 1) % 256;
        j = (j + box[a]) % 256;
        var tmp = box[a];
        box[a] = box[j];
        box[j] = tmp;

        result[i] = (byte)(source[i] ^ (box[(box[a] + box[j]) % 256]));
    }

    if (operation == AuthCodeMethod.Decode)
    {
        var time = long.Parse(BytesToString(SubBytes(result, 0, 10)));
        if ((time == 0 ||
                time - PhpTimeNow() > 0) &&
            BytesToString(SubBytes(result, 10, 16)) == BytesToString(SubBytes(Md5(AddBytes(SubBytes(result, 26), keyb)), 0, 16)))
        {
            return BytesToString(SubBytes(result, 26));
        }
        return "";
    }
    return BytesToString(keyc) + Convert.ToBase64String(result).Replace("=", "");
}

/// <summary>
/// Byte数组转字符串
/// </summary>
/// <param name="b">数组</param>
/// <returns></returns>
public static string BytesToString(byte[] b)
{
    return new string(Encode.GetChars(b));
}

/// <summary>
/// 计算Md5
/// </summary>
/// <param name="b">byte数组</param>
/// <returns>计算好的字符串</returns>
public static byte[] Md5(byte[] b)
{
    var cryptHandler = new MD5CryptoServiceProvider();
    var hash = cryptHandler.ComputeHash(b);
    var ret = "";
    foreach (var a in hash)
    {
        if (a < 16)
        { ret += "0" + a.ToString("x"); }
        else
        { ret += a.ToString("x"); }
    }
    return Encode.GetBytes(ret);
}

/// <summary>
/// Byte数组相加
/// </summary>
/// <param name="bytes">数组</param>
/// <returns></returns>
public static byte[] AddBytes(params byte[][] bytes)
{
    var index = 0;
    var length = 0;
    foreach(var b in bytes)
    {
        length += b.Length;
    }
    var result = new byte[length];

    foreach(var bs in bytes)
    {
        foreach (var b in bs)
        {
            result[index++] = b;
        }
    }
    return result;
}

/// <summary>
/// Byte数组分割
/// </summary>
/// <param name="b">数组</param>
/// <param name="start">开始</param>
/// <param name="length">结束</param>
/// <returns></returns>
public static byte[] SubBytes(byte[] b, int start, int length = int.MaxValue)
{
    if (start >= b.Length) return new byte[0];
    if (start < 0) start = 0;
    if (length < 0) length = 0;
    if (length>b.Length || start + length > b.Length) length = b.Length - start;
    var result = new byte[length];
    var index = 0;
    for(var k = start;k< start + length;k++)
    {
        result[index++] = b[k];
    }
    return result;
}

/// <summary>
/// 计算Php格式的当前时间
/// </summary>
/// <returns>Php格式的时间</returns>
public static long PhpTimeNow()
{
    return DateTimeToPhpTime(DateTime.UtcNow);
}

/// <summary>
/// PhpTime转DataTime
/// </summary>
/// <returns></returns>
public static DateTime PhpTimeToDateTime(long time)
{
    var timeStamp = new DateTime(1970, 1, 1); //得到1970年的时间戳
    var t = (time + 8 * 60 * 60) * 10000000 + timeStamp.Ticks;
    return new DateTime(t);
}

/// <summary>
/// DataTime转PhpTime
/// </summary>
/// <param name="datetime">时间</param>
/// <returns></returns>
public static long DateTimeToPhpTime(DateTime datetime)
{
    var timeStamp = new DateTime(1970, 1, 1);  //得到1970年的时间戳
    return (datetime.Ticks - timeStamp.Ticks) / 10000000;  //注意这里有时区问题,用now就要减掉8个小时
}

/// <summary>
/// 随机字符串
/// </summary>
/// <param name="lens">长度</param>
/// <returns></returns>
public static byte[] RandomBytes(int lens)
{
    var chArray = new[]
                        {
                            'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
                            'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
                            'H', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
                            'Y', 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'
                        };
    var length = chArray.Length;
    var result = new byte[lens];
    var random = new Random();
    for (var i = 0; i < lens; i++)
    {
        result[i] = (byte) chArray[random.Next(length)];
    }
    return result;
}

/// <summary>
/// 操作类型
/// </summary>
enum AuthCodeMethod
{
    Encode,
    Decode,
}

C# 版是一行一行按照原版本翻译的,增加了一些 C# 中没有的函数

 

1、string -> byte[] 的问题

在这段算法中,经常会用到 Base64 算法,C# 中的 Base64 要求输入的是 byte[] 数组

在 php 程序中,都是直接用字符串的,而且也没有问题。

那在 C# 版中自然想到了 Encoding.Default.GetBytes() 函数

但这个函数有个很奇怪的问题:

Encoding.UTF8.GetBytes(((char) 200).ToString())[0].ToString() //最后的值是多少?

运行一下后发现它不是200,因为这个函数涉及到了编码问题

所以上述的操作,如果直接对字符串操作,那会出现很多问题,因为 php 和 C# 对字符串使用的默认编码不同。

所以就改成了对 byte[] 进行操作

康盛旗下产品的搭建

1、UCenter

这个当然是最基本的东西,安装起来也很简单,官方就有教程

http://faq.comsenz.com/userguide/x/install.html

ucenter_success

安装完成后,因为还没有安装别的应用,所以应用数量是:0

2、Discuz

如果仅仅是为了用 UCenter,那有点得不偿失了,一般都会配上论坛

这里采用的是 Discuz! 7.2

http://faq.comsenz.com/userguide/discuz/install.html

connection

这里没有什么难度,网上有许多教程

 

 

Asp.net 测试网站的搭建

新建网站

既然是 UCenter 和 Asp.net 通讯,那肯定要搭建一个 Asp.net 的网站了

为了 方便测试,我们最好把网站直接在 IIS 中调试

新建网站应用程序 — 打开属性页面

ucentertest

这步非不要操作,但是可以模拟真实的场景,并且还可以在 IIS 里调试

 

设置完后我们看一下 IIS 里的情况(我把 UCenter 和 Discuz 挂在 IIS 下了)

 

在 UCenter 下新建应用程序

登录后点添加新应用

add_app

 

按照这张图配置一下

apps_settings

这里就一个地方和配置 php 的网站不同,就是“应用接口文件名称”

当然你也可以用 .php 然后配置 IIS,但是这个多麻烦?用 ashx 是最方便的,在后面会有详解,到时候你就知道为什么了

 

提交后复制一下配置信息,后面有用

app_info

 

配置Asp.net网站

接下来我们需要把配置文件写入 Asp.net 的网站的 Web.config 中

乍一看,这配置是 php 格式的!

但这里有一份完整的配置信息,只要替换对应的地方就行:

 

<!--客户端版本-->
<add key="UC_CLIENT_VERSION" value="1.5.2"/>
<!--发行时间-->
<add key="UC_CLIENT_RELEASE" value="20101001"/>

<!--API 开关(value类型:True False 默认值:True)-->
<!--是否允许删除用户-->
<add key="API_DELETEUSER" value="True"/>
<!--是否允许重命名用户-->
<add key="API_RENAMEUSER" value="True"/>
<!--是否允许得到标签-->
<add key="API_GETTAG" value="True"/>
<!--是否允许同步登录-->
<add key="API_SYNLOGIN" value="True"/>
<!--是否允许同步登出-->
<add key="API_SYNLOGOUT" value="True"/>
<!--是否允许更改密码-->
<add key="API_UPDATEPW" value="True"/>
<!--是否允许更新关键字-->
<add key="API_UPDATEBADWORDS" value="True"/>
<!--是否允许更新域名解析缓存-->
<add key="API_UPDATEHOSTS" value="True"/>
<!--是否允许更新应用列表-->
<add key="API_UPDATEAPPS" value="True"/>
<!--是否允许更新客户端缓存-->
<add key="API_UPDATECLIENT" value="True"/>
<!--是否允许更新用户积分-->
<add key="API_UPDATECREDIT" value="True"/>
<!--是否允许向UCenter提供积分设置-->
<add key="API_GETCREDITSETTINGS" value="True"/>
<!--是否允许获取用户的某项积分-->
<add key="API_GETCREDIT" value="True"/>
<!--是否允许更新应用积分设置-->
<add key="API_UPDATECREDITSETTINGS" value="True"/>
<!--API 开关结束-->

<!--返回值设置-->
<!--返回成功(默认:1)-->
<add key="API_RETURN_SUCCEED" value="1"/>
<!--返回失败(默认:-1)-->
<add key="API_RETURN_FAILED" value="-1"/>
<!--返回禁用(默认:-2)-->
<add key="API_RETURN_FORBIDDEN" value="-2"/>
<!--返回值设置结束-->

<!--[必填]通信密钥-->
<add key="UC_KEY" value="FD144298AF7E4797A66ACC0C18C97EA3"/>
<!--[必填]UCenter地址-->
<add key="UC_API" value="http://localhost/ucenter"/>
<!--[必填]默认编码-->
<add key="UC_CHARSET" value="utf-8"/>
<!--[非必填]UCenter IP-->
<add key="UC_IP" value=""/>
<!--[必填]应用ID-->
<add key="UC_APPID" value="2"/>

其中,除了标记必填的,别的都可以不填,默认值就是这个

Asp.net 网站算是搭建成功了,但是现在还没有用到那个类库呢!

 

 

类库的使用方法

类库概况

solution

 

类库分为以下几个部分

  1. Api 用于提供给 UCenter 调用的结构

  2. Client 用于调用 UCenter 的接口

  3. Model 调用过程中的一些数据封装

  4. UcConfig 静态类,读取上面的配置文件信息

  5. UcUtility 一些常用函数

  6. App.config 配置文件示例

 

 

调用 UCenter API

这步非常简单,只要配置好前面的东西,然后简单地调用一个类就行了。

 

IUcClient client = new UcClient();
var user = client.UserLogin("admin", "admin");//登陆
if (user.Success)//判断是否登陆成功
{
    client.PmSend(0, 0, "公告", "测试公告", user.Uid);//给该用户发送系统消息
}

 

那具体有哪些函数可以被调用呢?可以看一下IUcClient接口

 

using System.Collections.Generic;

namespace DS.Web.UCenter.Client
{
    ///<summary>
    /// UcApi客户端
    ///</summary>
    public interface IUcClient
    {
        /// <summary>
        /// 用户注册
        /// </summary>
        /// <param name="userName">用户名</param>
        /// <param name="passWord">密码</param>
        /// <param name="email">Email</param>
        /// <param name="questionId">登陆问题</param>
        /// <param name="answer">答案</param>
        /// <returns></returns>
        UcUserRegister UserRegister(string userName, string passWord, string email, int questionId = 0, string answer = "");

        /// <summary>
        /// 用户登陆
        /// </summary>
        /// <param name="userName">用户名/Uid/Email</param>
        /// <param name="passWord">密码</param>
        /// <param name="loginMethod">登录方式</param>
        /// <param name="checkques">需要登陆问题</param>
        /// <param name="questionId">问题ID</param>
        /// <param name="answer">答案</param>
        /// <returns></returns>
        UcUserLogin UserLogin(string userName, string passWord, LoginMethod loginMethod = LoginMethod.UserName, bool checkques = false, int questionId = 0, string answer = "");

        /// <summary>
        /// 得到用户信息
        /// </summary>
        /// <param name="userName">用户名</param>
        /// <returns></returns>
        UcUserInfo UserInfo(string userName);

        /// <summary>
        /// 得到用户信息
        /// </summary>
        /// <param name="uid">Uid</param>
        /// <returns></returns>
        UcUserInfo UserInfo(int uid);

        /// <summary>
        /// 更新用户信息
        /// 更新资料需验证用户的原密码是否正确,除非指定 ignoreoldpw 为 1。
        /// 如果只修改 Email 不修改密码,可让 newpw 为空;
        /// 同理如果只修改密码不修改 Email,可让 email 为空。
        /// </summary>
        /// <returns></returns>
        UcUserEdit UserEdit(string userName, string oldPw, string newPw, string email, bool ignoreOldPw = false, int questionId = 0, string answer = "");

        /// <summary>
        /// 删除用户
        /// </summary>
        /// <param name="uid">Uid</param>
        /// <returns></returns>
        bool UserDelete(params int[] uid);

        /// <summary>
        /// 删除用户头像
        /// </summary>
        /// <param name="uid">Uid</param>
        void UserDeleteAvatar(params int[] uid);

        /// <summary>
        /// 同步登陆
        /// </summary>
        /// <param name="uid">Uid</param>
        /// <returns>同步登陆的 Html 代码</returns>
        string UserSynlogin(int uid);

        /// <summary>
        /// 同步登出
        /// </summary>
        /// <returns>同步登出的 Html 代码</returns>
        string UserSynLogout();

        /// <summary>
        /// 检查 Email 格式
        /// </summary>
        /// <param name="email">Email</param>
        /// <returns></returns>
        UcUserCheckEmail UserCheckEmail(string email);

        /// <summary>
        /// 增加受保护用户
        /// </summary>
        /// <param name="admin">操作管理员</param>
        /// <param name="userName">用户名</param>
        /// <returns></returns>
        bool UserAddProtected(string admin, params string[] userName);

        /// <summary>
        /// 删除受保护用户
        /// </summary>
        /// <param name="admin">操作管理员</param>
        /// <param name="userName">用户名</param>
        /// <returns></returns>
        bool UserDeleteProtected(string admin, params string[] userName);

        /// <summary>
        /// 得到受保护用户
        /// </summary>
        /// <returns></returns>
        UcUserProtecteds UserGetProtected();

        /// <summary>
        /// 合并用户
        /// </summary>
        /// <param name="oldUserName">老用户名</param>
        /// <param name="newUserName">新用户名</param>
        /// <param name="uid">Uid</param>
        /// <param name="passWord">密码</param>
        /// <param name="email">Email</param>
        /// <returns></returns>
        UcUserMerge UserMerge(string oldUserName, string newUserName, int uid, string passWord, string email);

        /// <summary>
        /// 移除重名用户记录
        /// </summary>
        /// <param name="userName">用户名</param>
        void UserMergeRemove(string userName);

        /// <summary>
        /// 得到用户积分
        /// </summary>
        /// <param name="appId">应用程序Id</param>
        /// <param name="uid">Uid</param>
        /// <param name="credit">积分编号</param>
        /// <returns></returns>
        int UserGetCredit(int appId, int uid, int credit);

        /// <summary>
        /// 检查新消息
        /// </summary>
        /// <param name="uid">Uid</param>
        /// <returns></returns>
        UcPmCheckNew PmCheckNew(int uid);

        /// <summary>
        /// 发送短消息
        /// </summary>
        /// <param name="fromUid">发件人用户 ID,0 为系统消息</param>
        /// <param name="replyPmId">回复的消息 ID,0:发送新的短消息,大于 0:回复指定的短消息</param>
        /// <param name="subject">消息标题</param>
        /// <param name="message">消息内容</param>
        /// <param name="msgTo">收件人ID</param>
        /// <returns></returns>
        UcPmSend PmSend(int fromUid, int replyPmId, string subject, string message, params int[] msgTo);

        /// <summary>
        /// 发送短消息
        /// </summary>
        /// <param name="fromUid">发件人用户 ID,0 为系统消息</param>
        /// <param name="replyPmId">回复的消息 ID,0:发送新的短消息,大于 0:回复指定的短消息</param>
        /// <param name="subject">消息标题</param>
        /// <param name="message">消息内容</param>
        /// <param name="msgTo">收件人用户名</param>
        /// <returns></returns>
        UcPmSend PmSend(int fromUid, int replyPmId, string subject, string message, params string[] msgTo);

        /// <summary>
        /// 删除短消息
        /// </summary>
        /// <param name="uid">Uid</param>
        /// <param name="folder">文件夹</param>
        /// <param name="pmIds">短消息ID</param>
        /// <returns>删除的数量</returns>
        int PmDelete(int uid, PmDeleteFolder folder, params int[] pmIds);

        /// <summary>
        /// 删除会话
        /// </summary>
        /// <param name="uid">发件人</param>
        /// <param name="toUids">收件人</param>
        /// <returns>删除的数量</returns>
        int PmDelete(int uid, params int[] toUids);

        /// <summary>
        /// 修改阅读状态
        /// </summary>
        /// <param name="uid">发件人</param>
        /// <param name="toUids">收件人</param>
        /// <param name="pmIds">短消息ID</param>
        /// <param name="readStatus">阅读状态</param>
        void PmReadStatus(int uid, int toUids, int pmIds = 0, ReadStatus readStatus = ReadStatus.Readed);

        /// <summary>
        /// 修改阅读状态
        /// </summary>
        /// <param name="uid">发件人</param>
        /// <param name="toUids">收件人数组</param>
        /// <param name="pmIds">短消息ID数组</param>
        /// <param name="readStatus">阅读状态</param>
        void PmReadStatus(int uid, IEnumerable<int> toUids, IEnumerable<int> pmIds, ReadStatus readStatus = ReadStatus.Readed);

        /// <summary>
        /// 获取短消息列表
        /// </summary>
        /// <param name="uid">Uid</param>
        /// <param name="page">当前页编号,默认值 1</param>
        /// <param name="pageSize">每页最大条目数,默认值 10</param>
        /// <param name="folder">短消息所在的文件夹</param>
        /// <param name="filter">过滤方式</param>
        /// <param name="msgLen">截取短消息内容文字的长度,0 为不截取,默认值 0</param>
        /// <returns></returns>
        UcPmList PmList(int uid, int page = 1, int pageSize = 10, PmReadFolder folder = PmReadFolder.NewBox, PmReadFilter filter = PmReadFilter.NewPm, int msgLen = 0);

        /// <summary>
        /// 获取短消息内容
        /// 本接口函数用于返回指定用户的指定消息 ID 的消息,返回的数据中包含针对这个消息的回复。
        /// 如果指定 touid 参数,那么短消息将列出所有 uid 和 touid 之间的短消息,daterange 可以指定返回消息的日期范围。
        /// </summary>
        /// <param name="uid">Uid</param>
        /// <param name="pmId">短消息ID</param>
        /// <param name="toUid">收件人ID</param>
        /// <param name="dateRange">日期范围</param>
        /// <returns></returns>
        UcPmView PmView(int uid, int pmId, int toUid = 0, DateRange dateRange = DateRange.Today);

        /// <summary>
        /// 获取单条短消息内容
        /// </summary>
        /// <param name="uid">Uid</param>
        /// <param name="type">类型</param>
        /// <param name="pmId">短消息ID</param>
        /// <returns></returns>
        UcPm PmViewNode(int uid, ViewType type = ViewType.Specified, int pmId = 0);

        /// <summary>
        /// 忽略未读消息提示
        /// </summary>
        /// <param name="uid">Uid</param>
        void PmIgnore(int uid);

        /// <summary>
        /// 得到黑名单
        /// </summary>
        /// <param name="uid">Uid</param>
        /// <returns></returns>
        UcPmBlacklsGet PmBlacklsGet(int uid);

        /// <summary>
        /// 设置黑名单为禁止所有人(清空原数据)
        /// </summary>
        /// <param name="uid">Uid</param>
        /// <returns></returns>
        bool PmBlacklsSetAll(int uid);

        /// <summary>
        /// 设置黑名单(清空原数据)
        /// </summary>
        /// <param name="uid">Uid</param>
        /// <param name="userName">黑名单用户名</param>
        /// <returns></returns>
        bool PmBlacklsSet(int uid, params string[] userName);

        /// <summary>
        /// 添加黑名单为禁止所有人
        /// </summary>
        /// <param name="uid">Uid</param>
        /// <returns></returns>
        bool PmBlacklsAddAll(int uid);

        /// <summary>
        /// 增加黑名单
        /// </summary>
        /// <param name="uid">Uid</param>
        /// <param name="userName">黑名单用户名</param>
        /// <returns></returns>
        bool PmBlacklsAdd(int uid, params string[] userName);

        /// <summary>
        /// 删除黑名单中的禁止所有人
        /// </summary>
        /// <param name="uid">Uid</param>
        /// <returns></returns>
        void PmBlacklsDeleteAll(int uid);

        /// <summary>
        /// 删除黑名单
        /// </summary>
        /// <param name="uid">Uid</param>
        /// <param name="userName">黑名单用户名</param>
        void PmBlacklsDelete(int uid, params string[] userName);

        /// <summary>
        /// 增加好友
        /// </summary>
        /// <param name="uid">Uid</param>
        /// <param name="friendId">好友ID</param>
        /// <param name="comment">备注</param>
        /// <returns></returns>
        bool UcFriendAdd(int uid, int friendId, string comment = "");

        /// <summary>
        /// 删除好友
        /// </summary>
        /// <param name="uid">Uid</param>
        /// <param name="friendIds">好友ID</param>
        /// <returns></returns>
        bool UcFriendDelete(int uid, params int[] friendIds);

        /// <summary>
        /// 获取好友总数
        /// </summary>
        /// <param name="uid">Uid</param>
        /// <param name="direction">方向</param>
        /// <returns>好友数目</returns>
        int UcFriendTotalNum(int uid, FriendDirection direction = FriendDirection.All);

        /// <summary>
        /// 好友列表
        /// </summary>
        /// <param name="uid">Uid</param>
        /// <param name="page">当前页编号</param>
        /// <param name="pageSize">每页最大条目数</param>
        /// <param name="totalNum">好友总数</param>
        /// <param name="direction">方向</param>
        /// <returns></returns>
        UcFriends UcFriendList(int uid, int page = 1, int pageSize = 10, int totalNum = 10, FriendDirection direction = FriendDirection.All);

        /// <summary>
        /// 积分兑换请求
        /// </summary>
        /// <param name="uid">Uid</param>
        /// <param name="from">原积分</param>
        /// <param name="to">目标积分</param>
        /// <param name="toAppId">目标应用ID</param>
        /// <param name="amount">积分数额</param>
        /// <returns></returns>
        bool UcCreditExchangeRequest(int uid, int from, int to, int toAppId, int amount);

        ///<summary>
        /// 修改头像
        ///</summary>
        ///<param name="uid">Uid</param>
        ///<param name="type"></param>
        ///<returns></returns>
        string Avatar(int uid, AvatarType type = AvatarType.Virtual);

        /// <summary>
        /// 得到头像地址
        /// </summary>
        /// <param name="uid">Uid</param>
        /// <param name="size">大小</param>
        /// <param name="type">类型</param>
        /// <returns></returns>
        string AvatarUrl(int uid,AvatarSize size,AvatarType type = AvatarType.Virtual);

        /// <summary>
        /// 检查头像是否存在
        /// </summary>
        /// <param name="uid"></param>
        /// <param name="size"></param>
        /// <param name="type"></param>
        /// <returns></returns>
        bool AvatarCheck(int uid, AvatarSize size = AvatarSize.Middle, AvatarType type = AvatarType.Virtual);

        /// <summary>
        /// 获取标签数据
        /// </summary>
        /// <param name="tagName">标签名</param>
        /// <param name="number">应用程序ID对应的数量</param>
        /// <returns></returns>
        UcTags TagGet(string tagName, IEnumerable<KeyValuePair<string, string>> number);

        /// <summary>
        /// 添加事件
        /// </summary>
        /// <param name="icon">图标类型,如:thread、post、video、goods、reward、debate、blog、album、comment、wall、friend</param>
        /// <param name="uid">Uid</param>
        /// <param name="userName">用户名</param>
        /// <param name="titleTemplate">标题模板</param>
        /// <param name="titleData">标题数据数组</param>
        /// <param name="bodyTemplate">内容模板</param>
        /// <param name="bodyData">模板数据</param>
        /// <param name="bodyGeneral">相同事件合并时用到的数据:特定的数组,只有两项:name、link,保留</param>
        /// <param name="targetIds">保留</param>
        /// <param name="images">相关图片的 URL 和链接地址。一个图片地址,一个链接地址</param>
        /// <returns></returns>
        int FeedAdd(FeedIcon icon, int uid, string userName, string titleTemplate, string titleData, string bodyTemplate, string bodyData, string bodyGeneral, string targetIds, params string[] images);

        /// <summary>
        /// 得到Feed
        /// </summary>
        /// <param name="limit">数量限制</param>
        /// <returns></returns>
        UcFeeds FeedGet(int limit);

        /// <summary>
        /// 得到应用列表
        /// </summary>
        /// <returns></returns>
        UcApps AppList();

        /// <summary>
        /// 添加邮件到队列
        /// </summary>
        /// <param name="subject">标题</param>
        /// <param name="message">内容</param>
        /// <param name="uids">Uid</param>
        /// <returns></returns>
        UcMailQueue MailQueue(string subject, string message,params int[] uids);

        /// <summary>
        /// 添加邮件到队列
        /// </summary>
        /// <param name="subject">标题</param>
        /// <param name="message">内容</param>
        /// <param name="emails">目标Email</param>
        /// <returns></returns>
        UcMailQueue MailQueue(string subject, string message, params string[] emails);

        /// <summary>
        /// 添加邮件到队列
        /// </summary>
        /// <param name="subject">标题</param>
        /// <param name="message">内容</param>
        /// <param name="uids">Uid</param>
        /// <param name="emails">目标email</param>
        /// <returns></returns>
        UcMailQueue MailQueue(string subject, string message, int[] uids, string[] emails);

        /// <summary>
        /// 添加邮件到队列
        /// </summary>
        /// <param name="subject">标题</param>
        /// <param name="message">内容</param>
        /// <param name="fromMail">发信人,可选参数,默认为空,uc后台设置的邮件来源作为发信人地址</param>
        /// <param name="charset">邮件字符集,可选参数,默认为gbk</param>
        /// <param name="htmlOn">是否是html格式的邮件,可选参数,默认为FALSE,即文本邮件</param>
        /// <param name="level">邮件级别,可选参数,默认为1,数字大的优先发送,取值为0的时候立即发送,邮件不入队列</param>
        /// <param name="uids">Uid</param>
        /// <param name="emails">目标email</param>
        /// <returns></returns>
        UcMailQueue MailQueue(string subject,string message,string fromMail,string charset,bool htmlOn,int level,int[] uids,string[] emails);
    }
}

这份 API 是根据 UCenter API 开发手册开发的

 

 

所有的API都在里面了,不用考虑实现细节,配置好以后直接调用即可!

 

供 UCenter 调用的接口

这里,我们现在网站下新建一个叫 API 的文件夹(一定要叫 API)

然后再创建一个 ashx 文件(文件名和前面的配置对应即可,上面用的是 uc.ashx ,只要对应即刻,没必要用 uc.php)

 

结构如下:

ashx

uc.ashx 修改如下:

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using DS.Web.UCenter.Api;

namespace DS.Web.UCenter.Website.API
{
    /// <summary>
    /// Summary description for uc
    /// </summary>
    public class uc:UcApiBase
    {
        public override ApiReturn DeleteUser(IEnumerable<int> ids)
        {
            throw new NotImplementedException();
        }

        public override ApiReturn RenameUser(int uid, string oldUserName, string newUserName)
        {
            throw new NotImplementedException();
        }

        public override UcTagReturns GetTag(string tagName)
        {
            throw new NotImplementedException();
        }

        public override ApiReturn SynLogin(int uid)
        {
            throw new NotImplementedException();
        }

        public override ApiReturn SynLogout()
        {
            throw new NotImplementedException();
        }

        public override ApiReturn UpdatePw(string userName, string passWord)
        {
            throw new NotImplementedException();
        }

        public override ApiReturn UpdateBadWords(UcBadWords badWords)
        {
            throw new NotImplementedException();
        }

        public override ApiReturn UpdateHosts(UcHosts hosts)
        {
            throw new NotImplementedException();
        }

        public override ApiReturn UpdateApps(UcApps apps)
        {
            throw new NotImplementedException();
        }

        public override ApiReturn UpdateClient(UcClientSetting client)
        {
            throw new NotImplementedException();
        }

        public override ApiReturn UpdateCredit(int uid, int credit, int amount)
        {
            throw new NotImplementedException();
        }

        public override UcCreditSettingReturns GetCreditSettings()
        {
            throw new NotImplementedException();
        }

        public override ApiReturn GetCredit(int uid, int credit)
        {
            throw new NotImplementedException();
        }

        public override ApiReturn UpdateCreditSettings(UcCreditSettings creditSettings)
        {
            throw new NotImplementedException();
        }
    }
}

本来呢,ashx 继承的是 IHttpHandler ,但是呢,我们需要修改一下,让它继承 UcApiBase

它是一个抽象类,重写抽象方法即可。

 

但是具体怎么用呢?

这些函数不是给你调用的,是给 UCenter 调用的,你要做的就是写一些逻辑代码。比如 UCenter 告诉你 有人登陆了 (SynLogin函数)

那你应该做点什么呢? 写 Cookie ?写 Session ? 都行~

同样,当 UCenter 同步登出的时候,你也需要写一些逻辑代码,清理 Cookie 或者 Session

另外几个函数是干嘛的呢? 参考 UCenter 接口开发手册中的 API接口

MVC 网站下的用法

前一段时间在 MVC 的网站中使用了自己的 UCenter API

但是出现了一个问题:

MVC 下可以建立静态文件,路由的时候如果存在静态文件则直接访问,包括 aspx, asxh 等文件。

像原来一样,建立了 uc.ashx 文件,但是在使用的时候却出现了一个问题:无法访问 Session

HttpContext 里的 Session 对象是 null

就算继承了 IRequiresSessionStat 接口后还是一样

那如何解决呢?

本来想从底层想办法,但是发现 Controller 差异太大,所以放弃。

后来发现,其实有个很简单的方法,直接在 Controller 里新建该对象即可。

 

namespace MVC.Controllers
{
    public class APIController : Controller
    {
        public ActionResult Uc()
        {
            new UcBase().ProcessRequest(System.Web.HttpContext.Current);
            return Content("");
        }

    }
}


转载于:https://my.oschina.net/zhuxuan/blog/309597

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值