这两天常常和guolin同学讨论程序员的一些事情,其实说的不多,也没什么特别总结的。早上做了一点东西反倒有点灵光一现的感觉,所以记录下来。既然简单,那么也说的简单点。
嗨,说白了,设计如果太复杂,那么问题也多,以后也会有很多问题。特别是大一点的web应用,一般来说都是算法算数据,前端展现,有时候需要隐藏某些数据的时候,都采用前端去做一下筛选的方法。想想看,这种做法即不简单、也不直白,而且也没解决问题。随着逻辑的增长,反而束手束脚,带来很多问题。最简单的方法就是在算法的表里把数据去掉就行了,而前端只要读出数据就成。
不过话说回来,虽然说的简单,做起来却难,先不说公司那么多人、上级下级的问题。即便是自己,如果没有一定的洁癖,估计也会想着“姑且就这么快点实现需求吧”这样的事情吧。所以问题就总是一拖再拖,最后再来个彻底的重构,可是怎么想也不是划算和值得提倡的东西。
所以,遇到了这样的问题,还不如从自己开始,马上开始动手,否则复杂的东西就没有意义了。
最近看了mixi的memcached的分布式的一些入门文章,虽然有些知识算是比较老的知识了,但是还是做一下笔记,虽然拖了好久都没有写(以致于我tab打开都不敢关,怕没感觉了),今天终于下定决心写了。。以后可以信手拈来拿来看,也算做一个备份整理。
我大概看了几篇相关的文章,一起整理了。
豆瓣条目在这里,翻译在这里,原文在这里。
简单的记录一下。memcached的server一般不做事,即便是分布式,memcached的server端也很简单,主要是客户端来做这个逻辑处理。也就是说,当获取一个key,value对的时候,根据key来做一个hash,然后根据hash存储在服务器上面。这个过程很简单,但是存储的方法有很多。
首先介绍根据余数计算分散。大概逻辑如下。
还有一些链接:
Consistent Hashing
スマートな分散で快適キャッシュライフ
最近一直在做移动设备的Google Analytics(后面称GA),遇到了很多问题,尝试了很多办法,最后勉强做出了一个基本可用的东西,虽然成果不怎样,但是还是记录一下这个过程中遇到的问题吧,方便以后查看,如果有遇到相同问题的朋友又解决了的话,希望能够给我一些意见。
Google官方移动版部署的可以看这里。
一开始,一切显得那么美好,访问了Google官方的移动版页面之后,发现貌似很容易,因为Google官方网站上都有了Sample Code,下下来看一下,测试一下,只要有了数据就说明ok了,虽然没有python版本,但是自己捣腾一下改写成一个python版本的也不是大问题,弄完了之后放到网站上,嗯,发现要等很长时间才能获得数据,ok,等,等了第二天之后,发现果然有数据了,虽然数据数为1,但是说明数据还是传达到了。
但是,等等,连续过了5天,发现还是1,说明数据有问题啊,仔细检查了代码和参数,发现没有什么问题。与此同时,运维的同学来找我了,说是这PV不对啊,翻翻了(因为官网是在服务器端发送请求的),所以要改,所以就tail日志发送到GA。不知道GA是否会去访问手机浏览器的cookie,如果访问的话,凑参数可能还不能完全凑正确,不过看了几个参数,都是可以凑的,否则还不能手动的去向GA发送数据。
其实,最主要的问题还是数据为1这个,这个问题真头疼,问了新闻组也没人回复,调整参数还要8个到12个小时才有结果,非常郁闷,后来我搜了一下国外的新闻组,发现,有很多人遇到了跟我一样的问题,而且Google没有回复,后来好不容易搜到了一个解决方法,这个方法非常的magic,虽然不好理解,但是还是奏效了,例子可以看这里。
最后发现,核心的问题就在于Google给的Sample里面url是这样的。
http://www.google-analytics.com/__utm.gif?utmwv=4.4sp&utmn=1265992655&utmhn=www.mysite.gov&utmr=-&utmp=%2FBLSW%2Fpdfs%2FAge_Of_Majority.pdf&utmac=UA-10916562-4&utmcc=__utma%3D999.999.999.999.999.1%3B&utmvid=0x72137fa74c3d8c6e&utmip=68.54.126.0
其中,utma应该不是999.999这样一致的数字,一开始我也怀疑了,但是没有证据,毕竟源代码也是这样写的,于是我估计GA可能会在接受参数的时候进行编码等操作,但是我测试过,好像并不是这样,或者参数有什么固定的顺序?真是太不好调试了,所以测了几天都没有效果。然后看了上面的那篇文章,据说是把_utm.gif里面的逻辑逆向了,于是就有了如此magic的代码。
def gen_utma():
domain_name = ‘www.jguoer.com‘
domain_hash = 0
g = 0
i = len(domain_name) - 1
while i>=0:
c = ord(domain_name[i])
domain_hash = ((domain_hash << 6) & 0xfffffff) + c + (c << 14)
g = domain_hash & 0xfe00000
if g!=0:
domain_hash = domain_hash ^ (g >> 21)
i = i - 1
rnd_num = str(randint(1147483647, 2147483647))
time_num = str(time.time()).split(‘.‘)[0]
_utma = ‘%s.%s.%s.%s.%s.%s‘ % (domain_hash, rnd_num, time_num,
time_num, time_num, 1)
return _utma
上面的代码太magic,但是是很必要的,就是为了替换奇怪的999.999那串字符的,如果不这么做的话,估计连最基本的访问量的数字都没有。不过,从另一点来看这个代码,但是这里只是将域名给hash了,并没有任何其他的参数,所以也没有什么其他的信息,至于Google文档里面的utmvid和utmip参数可有可无,不知道如何修改。
好在最后,最最基本的功能还是达到了。其他的参数还在研究中,我将代码发布到了github上面,并且一解决问题我就会更新源代码。源代码可以看这里。
如果,你真的遇到过我这样的问题并且解决了的话,希望能提点提点我。
做开源的东西已经一段时间了,从微软阵营转到开源的几个月是非常痛苦的。
曾经一直认为学好了一门语言之后,其他的语言学起来都是相通的,但是现在觉得这句话还是有失偏颇的,因为一门语言背后的是一个社区的风格习惯,这样的风格习惯会导致项目开发产生不同的编码,生产风格,最后导致项目中出现一些问题。
其实,了解这些差异也是很重要的。
当然,选择的路不同,到最后的习惯和方向也会很不同,这也就是前面说的那句话有失偏颇的问题。如果.Net接触多了,你写web service会考虑使用WCF之流,自然也会做出“相应”的社区中认为较好的应用或框架,但是拿到另一个社区可能完完全全就是垃圾,因为信奉的“宗教”不同,自然就会有问题。
当然,世界上还是有一些公理的,如如何写一个好的软件,设计模式等。
经常需要写一些小工具,这些工具有时候简单的就用python,性能高一点的就需要c,而我又喜欢用命令行和vi写程序,所以写makefile自然就成了麻烦中的麻烦了,不过好在可以使用automake,不过网上并没有很详细的说automake如何使用(都是东说一块西说一块的),查了一下然后整理了一下,发上来,就当自己做笔记,也方便了后面的人,高手自然自动飘过即可。
首先我们假设在这个目录下已经有一些代码了,例如main.c,我们使用automake来创建makefile,步骤如下。
1)首先在目录下运行autoscan。
2)修改configure.scan为configure.in。
3)编辑configure.in
# -*- Autoconf -*-
# Process this file with autoconf to produce a configure script.
AC_PREREQ(2.61)
# 这下面都是需要填写的内容,如包的名称,版本号和bug报告的邮箱
AC_INIT(FULL-PACKAGE-NAME, VERSION, BUG-REPORT-ADDRESS)
AC_CONFIG_SRCDIR([main.c])
AC_CONFIG_HEADER([config.h])
# Checks for programs.
AC_PROG_CC
# Checks for libraries.
# Checks for header files.
# Checks for typedefs, structures, and compiler characteristics.
# Checks for library functions.
AC_OUTPUT
我们可以看到上面的默认配置,我们修改一下配置,修改后就成了下面这个样子。
# -*- Autoconf -*-
# Process this file with autoconf to produce a configure script.
AC_PREREQ(2.61)
# 添加我们的配置,如包的名称为helloworld,版本0.1,邮箱等
AC_INIT(helloworld, 0.1, soundbbg@gmail.com)
AC_CONFIG_SRCDIR([main.c])
AC_CONFIG_HEADER([config.h])
# 添加一些基本配置
AM_INIT_AUTOMAKE(main, 1.0)
# Checks for programs.
AC_PROG_CC
# Checks for libraries.
# Checks for header files.
# Checks for typedefs, structures, and compiler characteristics.
# Checks for library functions.
# 在这里添加输出makefile
AC_OUTPUT([makefile])
4)修改完成后退出,运行aclocal。
5)运行完成后运行autoheader
6)完成之后,我们创建一个makefile.am,并修改如下。
AUTOMAKE_OPTIONS=foreign
bin_PROGRAMS=main
main_SOURCES=main.c
7)修改完成后运行automake –add-missing
8)运行完成后运行./configure
OK我们的makefile就生成好了,这个时候只要我们make一下就可以编译程序了。编译完成后可以运行./xxxx 来运行自己的程序。
由于使用了Google App Engine,而且也经常使用Mako,觉得Mako的语法其实挺不错的,而且也比较容易理解,性能也不错,所以就想在Google App Engine上使用Mako,尝试了一下,并不是很难,具体做法如下。
1.下载mako http://www.makotemplates.org/downloads/Mako-0.2.5.tar.gz,可以使用wget url命令获取,Mac下需要下载一个wget,我习惯用wget了。
2.解压缩。
3.安装应该在/资源库/Python/2.5(或你使用的版本)/site-packages/Makoxxxxxx/mako。
4.拷贝上面的资源到你的应用程序目录下。
当然,还要写一点代码去使用mako,如果你不愿意去实现,可以看看我怎么做,Go on。
在上一篇文章中,我写了一个项目模板,就是一个基本上完整的,可以扩展和维护的项目的结构,这里,我更改了一些项目的模板,这个项目的模板的设计可以很方便的使用Mako模板实现,这个结构我就不多说了,具体在这里可以看到。
这个模板里我加了一个lib/template.py,用于包装Mako一些方法,代码可以自己下了看。在public里面,包装了一些静态文件的路径的变量,以便以后可以只需要更改一个地方,方便维护,并将Django模板的html变成Mako模板。
使用方法:
引用
from lib.template import st
使用
self.response.out.write(st(“main.mako”, **locals()))
其中main.mako已经设定为在template之下
下载
makosample
今天有点时间,于是想找一个支持Python的服务器,后来很失望,因为国内几乎没有Python的服务器,好吧,只能说我自己的期望有点高,但是又不想自己弄一个机器当服务器还配置很多东西,于是就选择了Google App Engine。

Google App Engine可以当作是一个托管的开发平台,有一套自己的环境,我们只需要下载SDK安装并且使用相应的工具开发,然后部署即可,非常简单,因为我喜欢Python,于是就使用Python作为开发环境。
首先注册一个App Engine帐户,如果有Google帐户的话,那么就很容易,直接创建一个应用。创建很简单,就如下图即可,创建之后就有控制板等告诉你谁访问过之类的,这里我就不废话了。

例如这里我创建了一个名字叫jguoer的应用,这是个标志,后面我们需要,创建完成之后我们可以去这个页面下载SDK和工具,当然,我是Mac OS,你可以下载属于你的平台的SDK,很小,很轻量。
下面就是Mac OS X里的内容了,下载完成之后,打开安装的程序,如下图。

我们可以单击“+”添加一个应用,这个应用的名字必须和我们前面创建的应用的名字相同,创建很简单,输入名字就行,创建完成后,我们可以点上面的Run运行我们的程序(通过127.0.0.1:port端口号访问),也可以点Deploy部署到网站上去,然后通过前面设置的域名访问。
我们创建一个应用程序后,我们可以在相应的位置找到文件,默认情况下只有3个文件,分别是两个yaml配置文件,然后一个是main.py文件,返回的是一个“Hello World”,直接运行就能够看到这个Hello World。当然,这样并不好,我们还需要有很多其他的文件,并创建一个更好的结构,更加可读,可扩展,可维护,这样就最好了,于是我自己改了一些结构,可以参考着使用。
可以在这里下载:
sample
其中的目录实际上被我改成了routing.py,controller,template,lib,stylesheets等目录,其中routing是专门设置Url规范的,controller和template分别放py和html模板,lib放公用的库文件,stylesheets等自然是放样式表等静态文件了。当然,如果要放js等文件的话,可以修改app.yaml文件。现在默认是处理了stylesheets静态目录。
实际上使用Google App Engine跑一个应用程序是很简单的事情,例如http://jguoer.appspot.com/,就是一个只用5分钟就完成的网站。
更多,请参考这里。
已经工作了很长时间,一直也没有说一下这个“关于程序员坚守的思考”的问题,这个问题不是针对现在的公司,只是我这么长时间来工作的一些心得,特别是关于做为一个程序员一些心得。
在产品开发过程中,肯定是会有不同的人承担不同的职责并在团队中做自己相应的任务,每个人的目的都不同,比如产品的目的是要尽快的发布产品,而程序员的目的是要写出高质量的代码等等。实际上,写出高质量的代码并不是一件很难的事情,随着经验的增长和阅历的丰富,高质量的代码并不是一个难以攀爬的高峰,但是为什么产品会出现越来越慢或者越来越差的局面呢,我觉得这是有几个因素造成的。
首先是程序员的能力,虽然我们这个地方主要不会探讨程序员的能力。因为我们首先将程序员当成是有责任心,能力可佳的人,至少这样的人不在少数,如果说程序员不能在相应的时间里(并不是开会所说的时间)完成相应难度的任务,那么这个程序员本身就是不可靠的,那剩下的就没有讨论的意义。所以在讨论之前,我们都至少能够相信程序员是可以靠的住的(虽然代码性能和优化上不会太完美)。
当程序员这一方面没有问题之后,又是什么导致产品出现走下坡路的局面呢?我个人认为是沟通和计划,一个好的产品一定要有一个好的计划,并不能只局限于(或者说着重于)所谓的“快速发布”和“敏捷开发”,如果在整个团队中,使用了错误的“敏捷开发”并以“快速发布”作为指导的话,那么这个产品肯定会有局限性,因为速度和质量并不能完全的达到一致。
很简单,一个程序员可以一周时间开发一个产品,但是不代表这个产品就能够发布或者上线,因为无论是多么好的程序员,总会犯一些这样那样的错误,甚至还会有手抖的情况(比如多打了一个i或者之类的),那么就要花时间去修复和优化,但是产品人员和一些运营的人员可能不会理解,因为从他们的呈现的结果来看,他们的测试只是针对部分的功能进行测试,比如点了之后能不能用,能不能正确的安装,能不能跨平台等,所以他们的思考方式是从一个外在的而不是内在的因素去考虑,而程序员可能更多的是在扩展性,可持续性和维护性去考虑的。所以产品可能会觉得这个东西可以马上发布(甚至今天下午就能发布,只是有一些很小很小很小的问题),甚至认为做完一个产品就等于做好一个产品,这是非常不正确的。但是对于程序这边,一个小问题可能会有很多改动,甚至会造成重构等。所以,肯定是会有冲突的。
那么出了这种问题(而这种问题是会经常出现的,并不是一个团队存在的,可以说是几乎所有的团队都有这样的情况),一般如何解决呢。我觉得国内的一些公司主要是这样去解决的,因为产品的时间是一定的,几乎可以说是不能变的,那么就要从程序上改变,于是快速更改和hot fix就来的特别的多,那么这样几乎无法保证程序的质量,那么在后期的维护中,这个程序将会是一个重大的隐患,很可能在某一天就忽然爆发,然后危害整个产品,这样不仅危害到整个公司,整个团队,产品部门,甚至危害到程序员本身(和生存),于是就有了程序员的坚持问题。
这个时候,我认为程序员是应该坚持自己的一些“职业操守”的,也是我想说的一个关于程序员坚守的思考。程序员首先应该做的是考虑这款产品的持久性,如果自己还想在这里混下去,还想有更多的空间提高,那么就需要坚持这个职业操守,因为只有坚持了职业操守,那么产品的看不见的部分就能够更有持续性,维护性,提高整个团队的维护性能,也就间接的节约了成本,从而发挥了一个“好的”程序员的价值,也让自己的生存更具有持续性。
当然,这么说是很容易的,做起来很难,因为毕竟产品这一边肯定是有一个固定的计划的,就像我原来开发一些浏览器插件一样,况且每个人的想法也不同,比如要业绩,写Daily report的时候需要写一些东西才行,那么就需要逼着一些相关人员完成相关工作,这个时候很容易出现讨论(甚至争论)的情形,如果这个时候妥协进而放弃代码上的职业操守,那么很容易就会出现产品走下坡路和失败的情况,当然,如果不遵守,甚至可能出现争吵,团队内讧等情况,这样对项目更不好。
所以,在一定条件的允许下(例如公司氛围,团队氛围和公司的导向),如果公司愿意花更多的时间在产品质量上,而不是在产品速度上,那么程序员可以在一定程度上进行团队内部的讨论,然后和产品以及上级进行讨论,规划;如果公司更愿意执行“快速发布”,那么虽然这不是你我所想看到的,但是也只能执行,也就是说在这种公司去说程序员的职业操守是非常无聊的,简直就等于说废话,所以如果这样,那么没办法,该怎么做就怎么做(这样就会导致程序员想:反正也不是我的代码。导致质量下降),干活完了回家。
不过,相比之下,我更觉得程序员是有责任和义务对项目负责的,也就是说,无论公司的性质和氛围如何,好的程序员都应该提出自己的想法然后尽量的去实践,去说服其他相关人员,这样不仅能够让自己学到东西,也能够提高产品质量,这是大家都愿意看到的。
在实际工作中,我也经常遇到这样的问题,很多时候我选择了妥协,即妥协于产品和时间,虽然很多时候这个时间规划并不是很正确。妥协的直接结果就是我放弃了代码的复用和拆分,性能上的优化的考虑和性能上的测试,现在想想是非常不正确的,虽然某一个角度来说完成一个还不错的任务(及时发布),但是真正做的好不好和以后会不会有问题,只有我自己知道。也随着时间的增长和负责的项目越来越多,越来越大,我也更加深切的体会到“职业操守”对程序员来说是多么的重要。
所以,无论在任何项目,任何产品的开发或者是迭代过程中,都应该找到一个最好的切入点和切入方式,在一定的程度上尽量的保证程序员的职业操守的执行和坚持,是对于程序员来说最难实现,但是是最重要的。当然,一开始的计划和讨论也是非常必要的,虽然这不是我们讨论的范围(我们只讨论出现了这种情况下,程序员应该如何去做),而且也没有一个人,一个团队能够完整的规划好项目时间,说到哪一天就一定能发布。虽然,这种事情经常发生,也没有一个很好的解决方案,但是我希望自己以后能够在这方面多做努力,也希望这个行业的同行们多做努力,改善现在这样的整体环境和行业规范。让产品能够在一个良性的循环中逐渐成长。
其实很早就想写HTML5和CSS3的文章了,但是一直都没什么时间写,最近闲下来了,于是就写一下HTML5和CSS3的文章。HTML5出来了有一段时间了,各个主流浏览器基本上都能够支持HTML5,HTML5能够为我们做很多事情,并且轻松的就能够实现非常酷的效果。我们不仅能在网页前端制作上面可以使用HTML5和CSS3,我们还能够在浏览器插件中使用HTML5和CSS3(因为Chrome和Firefo浏览器都支持HTML5),那么我们不如来尝试写一个HTML5的网页或插件。
不过写之前,我们要看看为什么要使用HTML5。关于HTML5的概念,可以去这里看。
我们一般写一个HTML网页的时候,基本上会写如下代码。
<div id=”blog”>
<div id=”sidebar” class=”sidebarmain”>
</div>
<div id=”content”>
</div>
</div>
我们可以看到上面的代码是通过Table进化过来的,曾经有一段时间,网页开发人员喜欢使用Table去布局,但是现在,网站开发人员更喜欢使用div去布局,原因就是Table布局不够语义化,也不够方便。使用div去布局就能够语义化,比如 id,class。不过随着网络的发展,这样也有一些坏处,比如,重复的去写id让这个div更有意义,比如有一个id,又包括一个class等等。其实,最主要的还是语义上不清楚,对搜索引擎的优化不够好。那么有了HTML5,我们就可以写成如下代码。
<blog>
<sidebar>
</sidebar>
<content>
</content>
</blog>
按照上面的方式,我们的代码就更具备意义,所以,HTML5就是为我们解决了这样一个问题,我们无论在写网站还是写浏览器插件的时候,语义不仅能够方便我们的搜索引擎去抓取页面内容,另外一方面更能够方便我们去维护,因为语义,我们写CSS代码也更加简单。
blog
{
color
:black;
}
blog sidebar
{
float
:right;
}
blog content
{
float
:right;
}
这样,我们的CSS代码也更具有语义性了。好了,说了这么多,简单的来说,使用HTML和CSS3,我们可以去掉所有的class属性和id属性,让分层更加清晰,让代码更加可读,让结构更加简单。
说了这么多,终于可以开始写代码了,这里我写的是一个Chrome插件,不过可以用浏览器去访问,很简单,做的一个下拉菜单,有三种效果,我这里截取了两种效果的图。先看图。


好了,我们有了目标,就写相应的代码把。首先写HTML代码。
<!DOCTYPE html>
<html>
<head>
<title>Amazon Button</title>
<script type=”text/javascript” src=”js/jquery.js”></script>
<script type=”text/javascript” src=”js/main.js”></script>
<link type=”text/css” href=”css/css.css” rel=”stylesheet” />
</head>
<body>
<rmenu>
<rmenupopup>
<tmenu>
Bestsellers
<menupopup>
<tmenu>
Bestsellers Item1
<menupopup>
<menuitem>
Google
</menuitem>
</menupopup>
</tmenu>
<menuitem>
Bestsellers Item2
</menuitem>
<menuitem>
Bestsellers Item3
</menuitem>
</menupopup>
</tmenu>
<tmenu>
Bestsellers
</tmenu>
</rmenupopup>
</rmenu>
</body>
</html>
不用我多说,你也应该知道是什么意思了,tmenu是菜单,menupopup是菜单一个容器,menuitem就是每个菜单项了。OK,现在我们再来写CSS代码。
body
{
margin
:0px;
font-size
:12px;
font-family
:arial;
width
:400px;
height
:300px;
}
rmenu,rmenupopup,tmenu,menuitem,menupopup
{
display
:block;
margin
:0px;
color
:black;
}
tmenu,menuitem
{
background
:#f0f0f0;
border-bottom
:1px solid #e0e0e0;
border-top
:1px solid white;
min-width
:200px;
padding
:5px;
max-width
:500px;
}
tmenu
{
background
:#f0f0f0 url(“../images/dropdown.png”) no-repeat center right;
}
menupopup
{
border
:1px solid #ccc;
}
menupopup
{
display
:none;
margin-left
:10px;
}
a
{
text-decoration
:none;
color
:black;
}
CSS代码也是非常好理解的,现在我们就要写菜单的相应的JS代码了,这里我就不贴出JS代码了,代码可以从源代码中下载得到。
小结
其实最后我们从编程实例中可以看出,虽然HTML5和CSS3对开发人员来说没有特别好的编码方面的提升,但是在效果上和语义化上面有了很大的提升,节约了开发人员很多步骤。也随着HTML5的发展,在线视频,在线游戏,在线3D的提出和实现,可以看出未来网络的发展越来越快,越来越丰富,真正的基于浏览器的网络游戏和3D游戏也会随着时间和浏览器的支持流行起来,到时候网络应用和桌面应用的界限也会越来越模糊。
代码下载
Prototype.zip (183.52 kb)
这是一本一年多以前我写的一本书,但是没想到过了一段时间还是有人需要,这本书估计已经下市了,很多人直接给我发短信说买不到了,然后说找不到源代码以及光盘里的东西。我这里有一本作者用书,所以我这里有一点源代码,由于一开始不在我的手上,现在拿回来了,方便广大网友,所以就在这里提供源代码下载。本来有一个人说拿去做教材的,买不到书,说是要给他邮寄过去的,可惜我媳妇不想,于是作罢,对这个网友是非常不好意思,这里就提供源代码下载吧。
虽然这本书是我写的,但是有一年了,有很多网友还是会发邮件问我书的问题,我这本书是初学者的书,也是入门级别的,所以高手可以不需要看,然后对于入门的新手,我还是觉得能帮到别人一点算是一点,所以大家还是可以给我写邮件,不过注意基本的礼节,我也是要工作要吃饭会很忙的人,所以忙的时候不能解答问题,请见谅。书里面的内容和编写时间按照出版社要求所写,有错误或者有章节划分不好,也实在没有办法。
这本书可以到这里购买,源代码可以在这里下载。(说不定过一段时间就没了,没了的话再联系我,我再放上去)