第一幕:惊闻已沦僵尸粉,怒斩桃花正衣冠
今日晴空万里,清风徐徐,一觉醒来又是一个春日的早晨,额,咳咳,中午,,闲来无事打开久违的新浪微博,放眼望去,一千多个关注
,即知我苦逼的身居冷宫微博账号已沦为浩瀚僵尸粉大军中的一员,虽然十天半个月才上一次,但是被他人如此玩弄是万万不能忍的,此间心情犹如皇上得知自己七年没见过的嫔妃被某个忤逆臣子轻薄!
于是乎当机立断改密码,加绑定,然后删关注。
作为一名码农,虽然仅仅是低阶的草根屌丝码农,我也知道,一千多个关注一个个点不是我的风格,在找寻批量删除关注功能无果的情况下,毅然打开新浪微博开放平台,准备操刀来一发一键删除所有关注。
第二幕:看完文档轻抚蛋,码农荣耀难中求
浏览了一下微博的SDK列表,为了我的撸妹儿800毫不犹豫的选择了WP7的SDK(已升级2.0,官方只有release的库,网上有同志共享了源码,各位朋友有需要的请自行搜索。其实SDK并不复杂,API 全是REST的,看着文档稍微包一下Http的各种操作也是一样的)。
看完文档我一阵冷汗:俺滴亲娘喂,随便找个方法参数都如此蛋疼,SDK的作者真是我亲哥啊,否则拼实体类非拼到蛋碎不可。
但是看完SDK源码瞬间觉得胯下隐隐作痛,亲哥只给我包了这么几个接口,所需要的获取所有关注的功能,怎么木有啊啊啊啊啊啊~~~~
{
NULL_TYPE = -1,
FRIENDS_TIMELINE = 0, //获取下行数据集(timeline)接口(cmdNormalMessages)
UPLOAD_MESSAGE, //发送微博(cmdUploadMessage)
UPLOAD_MESSAGE_PIC, //发送带图片微博(cmdUploadPic)
FRIENDSHIP_CREATE, //关注某用户(cmdFriendShip)
FRIENDSHIP_DESDROY, //取消关注(cmdFriendShip)
FRIENDSHIP_SHOW, //获取两个用户关系的详细情况(cmdFriendShip)
AT_USERS, //@用户时的联想建议 (cmdAtUsers)
USER_TIMELINE, //获取用户发布的微博消息列表(cdmUserTimeline)
}
没办法了,抹了抹眼泪揉了揉蛋,自个儿加呗。
好在源码写的还是靠谱的,结构清晰一目了然,最疼的还是构造实体类,写了两行实在写不下去了,对着文档中的示例传瞅了又瞅,一拍大腿,喊道:"忍无可忍,哥今天要让你们自己变成代码!"。
第三幕:尼玛工欲善其事,妹的必先利其器(喵了个咪这样都让我凑够7个字了,真是才华横溢)
拿出专业的态度审视文档,心想总不能为新浪微博的API接口写一个代码生成器,哪天接腾讯微博又得写一个,日后不免被人耻笑。
因此决定打盘dota,平复一下思绪,选了个warlock,边打酱油边想需求,不料
一下子心满意足,各个dota群贴一下战绩炫耀一番之后,细细回想当年在盛大时候接别人接口时候各种从文档/代码/示例中提取实体类,还有写完代码拼文档时候的种种经历,提取出如下需求:
从待处理串OriginalString中通过自定义格式化串FormatString提取出所含参数列表ArgsList
再将 参数列表ArgsList 格式化到目标串TargetString中,最后替换待原串中满足格式化串FormatString的部分为目标串TargetString。
比较不好理解,用一个最复杂的场景举例说明(自然语言,复合链式处理):
需要将
"今天我买了4斤黄瓜,一斤2元,5斤茄子,一斤3元,6斤香蕉,一斤2.5元。(哟呵~)"
转换成
"4*2+5*3+6*2.5"
然后丢进计算器中算总价(这个可就不归我管了,嘿嘿)
那么
第一个转换
OriginalString = "今天我买了4斤黄瓜,一斤2元,5斤茄子,一斤3元,6斤香蕉,一斤2.5元。 "
FormatString = [[重量]]斤xx,一斤[[单价]]元
分析出3组满足条件的参数列表:
ArgsList = key: 重量, value: 4; key: 单价, value: 2
ArgsList = key: 重量, value: 5; key: 单价, value: 3ArgsList = key: 重量, value: 6; key: 单价, value: 2.5
TargetString = [[重量]]*[[单价]]
替换完成后应该是 "今天我买了4*2,5*3,6*2.5。 "
第二个转换
OriginalString = "今天我买了4*2,5*3,6*2.5。 "
FormatString = xxx[[算式1]],[[算式2]],[[算式3]]。
分析出满足条件的参数列表:
ArgsList = key: 算式1, value: 4*2; key: 算式2, value: 5*3; key: 算式3, value: 6*2.5
TargetString = [[算式1]]+[[算式2]]+[[算式3]]
替换完成后应该是 "4*2+5*3+6*2.5" (转换的方式多种多样,如果事前已知只有三组,也可以一次完成转换,这里仅作为示例引出下述工具的设计思路)
如此看来,这个,这个,噢 ,我还没给这工具起名呢,先起个名儿再说……
……
……
………………
恩,中文名就叫做 "春风化雨未有时,独上高楼自当歌" ,翻译成英文嘛就是 Onlr.Lazytools.CodeSnippet,这名字不错。
之前说到,这个 CodeSnippet 就的需求基本就明确了,这里罗列一下:
1. 核心功能:自定义的格式串匹配和替换。
2. 支持链式处理,以解耦太过复杂的转换需求。
3. 自由组织格式串定义,增删改查,自动识别等等等等,怎么懒怎么来!
界面功能浏览
1. 自动识别加载代码段定义文件。(程序集所在目录下的所有满足XSD定义的xml)
2. 增删改查,必须的!伦家才不改XML!
3. 全局自定义快捷键操作,在剪贴板内完成转换,keep silent。(选中待处理串 -> Ctrl + c -> Ctrl + Alt + Shift + D(自定义,可修改) -> Ctrl + V -> 完成 【傲娇】)
4. 链式转换在左边列表中复选即可,根据选中先后顺序执行。
实现部分在这里稍微整理一下主要逻辑:
核心转换器我只想到用正则表达式来实现,但是使用正则表达式就意味着贴上了【码农Only】的标签,实非本意,暂时只能提取接口保留其他实现方式的可能性。
{
/// <summary>
/// 获得转换器的原始代码段定义
/// </summary>
/// <returns></returns>
Snippet GetSnippet();
/// <summary>
/// str转换成预定义Snippet格式
/// </summary>
/// <param name="str"></param>
/// <returns></returns>
string Convert(string str);
}
正则表达式转换器的核心逻辑
m_WorkedOriginalFormat = Regex.Replace(SnippetDef.originFormat, REGEX_ARGS_DEFINE, new MatchEvaluator(AnalizeArg), RegexOptions.Multiline);
public override string Convert(string str)
{
return Regex.Replace(str, m_WorkedOriginFormat, new MatchEvaluator(ConvertCore), RegexOptions.Singleline);
}
/// 将待处理串中的满足格式的串进行替换操作
/// </summary>
/// <param name="m"></param>
/// <returns></returns>
string ConvertCore(Match m)
{
try
{
var targetStr = SnippetDef.targetFormat;
foreach (var arg in m_ArgsRegex)
{
// 提取参数值
var value = m.Groups[arg.Key].Captures[0].Value;
// 构造替换的目标串
targetStr = targetStr.Replace("[[" + arg.Key + "]]", value);
}
return targetStr;
}
catch (Exception)
{
throw new ArgumentException("待处理串格式不正确:" + m.Value);
}
}
其他的部分都是些细枝末节的东西了,譬如需要了解.Net正则表达式相关知识,复习一下C#如何使用windows api注册全局快捷键等等,酱油码农的特质一览无遗:用到什么什么不会。无所谓,股沟呗。
第四幕:哟呵快刀斩乱麻,啊哈倚楼听风雨 (这绝不是江郎才……才尽)
三下五除二工具完成,赶紧来一发,(⊙o⊙)…,赶紧用一下。
就拿上面贴出来的那个接口下刀。
这个请求神马的,太简单了,依葫芦画瓢在SDK源码中加个请求类型和cmd实体就行。
/// 参数uid与screen_name二者必选其一,且只能选其一,优先使用uid,均不填则取当前登录用户
/// </summary>
public class cmdFriends : SdkCmdBase
{
/// <summary>
/// 需要查询的用户UID。
/// </summary>
public string uid { get; set; }
/// <summary>
/// 需要查询的用户昵称。
/// </summary>
public string screen_name { get; set; }
public string count { get; set; }
public string cursor { get; set; }
}
关键的来了,返回的Json反序列化成实体。
首先,.Net下使用DataContractJsonSerializer可以轻松完成Json的序列化与反序列化,当然前提是,要有个实体类。刚完成的 "春风化雨未有时,独上高楼自当歌" V1.0 alpha版拿出来用呗。
定义格式串:
int类型
(?<=([\r\n]|^))\s*"[[key]]"\s*:\s*[[value||\d{1,15}]]\s*,?
public string [[key]] { get; set; }
其他同理
string类型 (?<=([\r\n]|^))\s*"[[key]]"\s*:\s*"[[value||[^\n]{0,200}]]"\s*,?
bool类型 (?<=([\r\n]|^))\s*"[[key]]"\s*:\s*[[value||true|false]]\s*,?
还有别忘了其他不认识的类型,例如文档中的
"geo": null,
"annotations": [],
不知道神马玩意,直接注释掉。 记得要放在执行链的最后,因为它匹配任何类型
(?<=([\r\n]|^))\s*[[other||"\w{1,50}"\s*:[^\r\n]*(?=[\r\n]|$)]]
((?<=([\r\n]|^)) 这个是断言, 其他的稍微懂点正则的应该都能看得懂,这里不再细说了,.Net正则表达式相关知识 )
复选并启动转换器,最小化它吧,咔咔。
先搞Status这个实体!
复制Json -> 轻声说:"接下来就是见证奇迹的时刻" -> 停顿3秒 -> 拨头发:"我要的不多,掌声,5秒钟" -> "5,4,3,2,1" -> Ctrl + Alt + Shift + D(自定义快捷键) -> 粘贴到VS中
呼,是的,当时就是这样。
接下来反正大家都懂了。
不管是从文档,从代码,从示例甚至从自然语言,还是转Java转C++转Python,只要499,只要499,第一个打进电话的再送金表一只……额,貌似,貌似,窜台了……,只要,你能通过正则表达式匹配出来的,"春风化雨未有时,独上高楼自当歌"都能转。
这里随便举几个我自己使用的例子:
Excel/SqlDbx的一列数据 转 SQL语句 (当年开发间技术客服的时候怎么没想起来写这个呢!)
定义:
<originFormat>[[orderid||\w{27}]]</originFormat>
<targetFormat>[[orderid]],</targetFormat>
</snippet>
<snippet name="orderIdList2SelectAllSQL_step1.5" comment="去掉最后一个逗号">
<originFormat>[[InvalidComma||,\s*$]]</originFormat>
<targetFormat></targetFormat>
</snippet>
<snippet name="orderIdList2SelectAllSQL_step2" comment="塞到select 中间">
<originFormat>[[orderIdList||^.*$]]</originFormat>
<targetFormat>select * from [order]
where orderid in (
[[orderIdList]]
)</targetFormat>
</snippet>
原始数据:
转换结果:
)
文档转代码
定义:
<originFormat>[[fieldname]]\s*\t[[type]]\s*\t[[comment||[^\n]*]](?=[\r\n]|$)</originFormat>
<targetFormat>//[[comment]]
[DataMember]
public [[type]] [[fieldname]] { get; set; }</targetFormat>
</snippet>
<snippet name="boolean2bool" comment="">
<originFormat>boolean</originFormat>
<targetFormat>bool</targetFormat>
</snippet>
<snippet name="int64_2_Int64" comment="">
<originFormat>int64</originFormat>
<targetFormat>Int64</targetFormat>
</snippet>
原始数据:
转换结果:
[DataMember]
public Int64 id { get; set; }
//用户昵称
[DataMember]
public string screen_name { get; set; }
//友好显示名称
[DataMember]
public string name { get; set; }
//用户所在地区ID
[DataMember]
public int province { get; set; }
//用户所在城市ID
[DataMember]
public int city { get; set; }
//用户所在地
[DataMember]
public string location { get; set; }
//用户描述
[DataMember]
public string description { get; set; }
//用户博客地址
[DataMember]
public string url { get; set; }
//用户头像地址
[DataMember]
public string profile_image_url { get; set; }
//用户的个性化域名
[DataMember]
public string domain { get; set; }
//性别,m:男、f:女、n:未知
[DataMember]
public string gender { get; set; }
//粉丝数
[DataMember]
public int followers_count { get; set; }
//关注数
[DataMember]
public int friends_count { get; set; }
//微博数
[DataMember]
public int statuses_count { get; set; }
//收藏数
[DataMember]
public int favourites_count { get; set; }
//创建时间
[DataMember]
public string created_at { get; set; }
//当前登录用户是否已关注该用户
[DataMember]
public bool following { get; set; }
//是否允许所有人给我发私信
[DataMember]
public bool allow_all_act_msg { get; set; }
//是否允许带有地理信息
[DataMember]
public bool geo_enabled { get; set; }
//是否是微博认证用户,即带V用户
[DataMember]
public bool verified { get; set; }
//是否允许所有人对我的微博进行评论
[DataMember]
public bool allow_all_comment { get; set; }
//用户大头像地址
[DataMember]
public string avatar_large { get; set; }
//认证原因
[DataMember]
public string verified_reason { get; set; }
//该用户是否关注当前登录用户
[DataMember]
public bool follow_me { get; set; }
//用户的在线状态,0:不在线、1:在线
[DataMember]
public int online_status { get; set; }
//用户的互粉数
[DataMember]
public int bi_followers_count { get; set; }
//用户的最近一条微博信息字段
[DataMember]
public object status { get; set; }
还有个小插曲,就是新浪微博的测试授权每小时每用户只能请求150次,因此我的1300多个关注,删了2天才全部删掉(想起来就打开撸妹儿点一下)。
最终幕:一壶浊酒喜相逢,古今多少事,都付笑谈中
后来,
后记
谨以此文分享思路。代码拙劣,就不献丑了。仅供低阶码农共同讨论学习,实在不入高端人士法眼,也请见谅。
另外,很多文本工具其实都自带了非常强悍的文本处理功能,文中提到的功能在实现上多数都是班门弄斧而已,仅为方便自己使用,效率、完备性等方面多数有欠考虑,虚心接受高人指点。