[{"content":"FoloToy AI 玩具是如何工作的？ FoloToy 出品的所有玩具（包括电路板）作为客户端连接玩具服务，玩具服务我们提供了folotoy-server的docker自部署镜像，可以轻易的部署在各种 Linux 发行版系统上，包括 Debian/Ubuntu/CentOS等等，自部署镜像配置下载地址为：\n自部署镜像：https://github.com/FoloToy/folotoy-server-self-hosting 部署教程：https://docs.folotoy.com/zh/docs/installation/start 玩具服务 folotoy-server 主要由以下3个部分组成 语音转文本：服务程序通过互联网接收玩具发送的实时录音数据，并调用 STT（声音转文本）API 将声音转换为文本。目前支持的 STT 选项包括：openai-whisper、azure-stt、azure-whisper、dify-stt、aliyun-asr 等，点击查看支持的STT完整列表。 调用大模型生成文本：在接收到上述文本后，立即调用 LLM（大型语言模型）API以流式方式获取 LLM 生成的句子。然后，调用 TTS（文本转语音）API 将句子转换为人类语音。目前支持的 LLM 选项包括：openai、 azure-openai、gemini、qianfan、dify或由 One-Api 代理的 LLM，以及与 OpenAI 接口兼容的其他大模型服务，如 moonshot 等， 点击查看支持的LLM或者Agent完整列表。 文本转语音：玩具将接收由FoloToy服务器生成的 TTS（文本转语音）音频文件流，并按顺序播放它们。目前支持的 TTS 选项包括： openai-tts、 azure-tts、 elevenlabs、 aliyun-tts、dify-tts、edge-tts(免费)等，点击查看支持的TTS完整列表。 FoloToy AI玩具调用的接口，支持了LLM行业的通行的做法，即完全兼容 OpenAI 的接口，在了解工作原理之后，只需要提供 OpenAI 接口格式相同RESTful接口，给folotoy-server调用，就能实现让玩具使用自定义的STT/LLM/TTS服务，如图所示：\n接下来介绍如何实现蓝色圆圈的部分服务\n实现和调用自定义服务 实现支持 OpenAI 接口的自定义服务之后可以通过修改 docker-compose.yml 或者 roles.json 文件来调用。docker-compose.yml 为修改全局变量，roles.json 可以针对角色进行修改，roles.json 配置的优先级高于 docker-compose.yml。\n自定义 STT 服务 接口说明 自行实现 STT 服务 RESTful 接口说明，详情请参考 OpenAI STT 接口：https://platform.openai.com/docs/api-reference/audio/createTranscription\n实现好接口之后需要支持以下的调用：\ncurl http://api.your_company.com/v1/audio/transcriptions \\ -H \u0026#34;Authorization: Bearer sk-xxxxxxxxxxxxxxxxxxxxxxxxx\u0026#34; \\ -H \u0026#34;Content-Type: multipart/form-data\u0026#34; \\ -F file=\u0026#34;@/path/to/file/audio.mp3\u0026#34; \\ -F model=\u0026#34;whisper-1\u0026#34; 配置使用 修改 docker-compose.yml 中的配置让 folotoy-server 使用自行实现的接口，如下：\nSTT_TYPE: openai-whisper OPENAI_WHISPER_API_BASE: http://api.your_company.com/v1 OPENAI_WHISPER_KEY: sk-xxxxxxxxxxxxxxxxxxxxxxxxx OPENAI_WHISPER_MODEL: whisper-1 修改好 docker-compose.yml 之后记得执行 sudo docker compose up -d 来重建服务\n参考实现 我们提供了开源的实现作为参考\n使用开源 faster-whisper 实现的 stt 服务：https://github.com/lewangdev/faster-whisper/blob/master/openaiapi.py 使用阿里开源的 SenseVoice 实现的 stt 服务：https://github.com/lewangdev/SenseVoice/blob/main/openaiapi.py 自定义 LLM 服务 接口说明 自行实现 LLM 服务 RESTful 接口说明，详情请参考 OpenAI LLM 接口：https://platform.openai.com/docs/api-reference/chat/create，玩具服务仅支持流式接口，即 stream 只能为 true\n实现好接口之后需要支持以下的调用：\ncurl http://api.your_company.com/v1/chat/completions \\ -H \u0026#34;Content-Type: application/json\u0026#34; \\ -H \u0026#34;Authorization: Bearer sk-xxxxxxxxxxxxxxxxxxxxxxxxx\u0026#34; \\ -d \u0026#39;{ \u0026#34;model\u0026#34;: \u0026#34;your_model_name\u0026#34;, \u0026#34;messages\u0026#34;: [ { \u0026#34;role\u0026#34;: \u0026#34;system\u0026#34;, \u0026#34;content\u0026#34;: \u0026#34;You are a helpful assistant.\u0026#34; }, { \u0026#34;role\u0026#34;: \u0026#34;user\u0026#34;, \u0026#34;content\u0026#34;: \u0026#34;Hello!\u0026#34; } ], \u0026#34;stream\u0026#34;: true }\u0026#39; 配置使用 修改 docker-compose.yml 中的配置让 folotoy-server 使用自行实现的接口，如下：\nLLM_TYPE: openai OPENAI_OPENAI_API_BASE: http://api.your_company.com/v1 OPENAI_OPENAI_MODEL: your_model_name OPENAI_OPENAI_KEY: sk-xxxxxxxxxxxxxxxxxxxxxxxxx 修改好 docker-compose.yml 之后记得执行 sudo docker compose up -d 来重建服务\n参考实现 我们提供了开源的实现作为参考\n使用开源 ChatGML3 实现的 llm 服务：https://github.com/THUDM/ChatGLM3/blob/main/openai_api_demo/api_server.py 自定义 TTS 服务 接口说明 自行实现 TTS 服务 RESTful 接口说明，详情请参考 OpenAI TTS 接口：https://platform.openai.com/docs/api-reference/audio/createSpeech\n实现好接口之后需要支持以下的调用：\ncurl https://api.openai.com/v1/audio/speech \\ -H \u0026#34;Authorization: Bearer sk-xxxxxxxxxxxxxxxxxxxxxxxxx\u0026#34; \\ -H \u0026#34;Content-Type: application/json\u0026#34; \\ -d \u0026#39;{ \u0026#34;model\u0026#34;: \u0026#34;tts-100\u0026#34;, \u0026#34;input\u0026#34;: \u0026#34;The quick brown fox jumped over the lazy dog.\u0026#34;, \u0026#34;voice\u0026#34;: \u0026#34;guodegang\u0026#34; }\u0026#39; \\ --output speech.mp3 配置使用 修改 docker-compose.yml 中的配置让 folotoy-server 使用自行实现的接口，如下：\nTTS_TYPE: openai-tts # OpenAI TTS OPENAI_TTS_API_BASE: http://api.your_company.com/v1 OPENAI_TTS_KEY: sk-xxxxxxxxxxxxxxxxxxxxxxxxx OPENAI_TTS_MODEL: tts-100 OPENAI_TTS_VOICE_NAME: guodegang 修改好 docker-compose.yml 之后记得执行 sudo docker compose up -d 来重建服务\n参考实现 我们提供了开源的实现作为参考\n使用网易开源的 EmotiVoice 实现的 tts 服务：https://github.com/netease-youdao/EmotiVoice/blob/main/openaiapi.py 使用阿里开源的 CosyVoice 实现的 tts 服务：https://github.com/lewangdev/CosyVoice/blob/main/openaiapi.py 使用 MyShell 开源的 OpenVoice 实现的 tts 服务：https://github.com/lewangdev/OpenVoice/blob/v1/openaiapi.py ","permalink":"https://lewang.dev/posts/2024-08-10-how-to-write-custom-api-for-your-folotoys/","summary":"FoloToy AI 玩具是如何工作的？ FoloToy 出品的所有玩具（包括电路板）作为客户端连接玩具服务，玩具服务我们提供了folotoy-server的docker自部署镜像，可以轻易的部署在各种 Linux 发行版系统上，包括 Debian/Ubuntu/CentOS等等，自部署镜像配置下载地址为：\n自部署镜像：https://github.com/FoloToy/folotoy-server-self-hosting 部署教程：https://docs.folotoy.com/zh/docs/installation/start 玩具服务 folotoy-server 主要由以下3个部分组成 语音转文本：服务程序通过互联网接收玩具发送的实时录音数据，并调用 STT（声音转文本）API 将声音转换为文本。目前支持的 STT 选项包括：openai-whisper、azure-stt、azure-whisper、dify-stt、aliyun-asr 等，点击查看支持的STT完整列表。 调用大模型生成文本：在接收到上述文本后，立即调用 LLM（大型语言模型）API以流式方式获取 LLM 生成的句子。然后，调用 TTS（文本转语音）API 将句子转换为人类语音。目前支持的 LLM 选项包括：openai、 azure-openai、gemini、qianfan、dify或由 One-Api 代理的 LLM，以及与 OpenAI 接口兼容的其他大模型服务，如 moonshot 等， 点击查看支持的LLM或者Agent完整列表。 文本转语音：玩具将接收由FoloToy服务器生成的 TTS（文本转语音）音频文件流，并按顺序播放它们。目前支持的 TTS 选项包括： openai-tts、 azure-tts、 elevenlabs、 aliyun-tts、dify-tts、edge-tts(免费)等，点击查看支持的TTS完整列表。 FoloToy AI玩具调用的接口，支持了LLM行业的通行的做法，即完全兼容 OpenAI 的接口，在了解工作原理之后，只需要提供 OpenAI 接口格式相同RESTful接口，给folotoy-server调用，就能实现让玩具使用自定义的STT/LLM/TTS服务，如图所示：\n接下来介绍如何实现蓝色圆圈的部分服务\n实现和调用自定义服务 实现支持 OpenAI 接口的自定义服务之后可以通过修改 docker-compose.yml 或者 roles.json 文件来调用。docker-compose.yml 为修改全局变量，roles.json 可以针对角色进行修改，roles.json 配置的优先级高于 docker-compose.yml。\n自定义 STT 服务 接口说明 自行实现 STT 服务 RESTful 接口说明，详情请参考 OpenAI STT 接口：https://platform.","title":"如何开发FoloToy玩具可以使用的私有 STT/LLM/TTS 服务"},{"content":"我的推特账号 x.com/lewangdev 终于解封了，从去年8月17号一直封到今天，已经差不多快有 6 个月的时间。在这 6 个月里，除了今年 1 月份，我每个月都写了一封申请解封的邮件，前几个月全部都是使用 ChatGPT 写的英文版解封申请，就在去年12月份的时候，想想自己莫名其妙被封确实有点恼火，于是用中文写了一封解封邮件，并且提到封我的账号是否是因为审核人员有种族歧视。以下是我的邮件内容：\nHi，\n已经过去四个月了，我的账号 lewangdev 还是被冻结着的，真是太难熬了。我翻看自己的推文，始终认为我没有发布任何破坏了推特的规则的推文，冻结我的账号一定是一个误会。\n我已经使用这个账号十多年了，一直在个人使用，我的账号被冻结不仅是我感觉到惊愕，我的所有朋友们都觉得太莫名奇妙了，让大家都觉得推特平台怎么会这样不确定。\n另外我不知道你们为什么会封禁我的账号，是因为我一个亚洲人吗，如果不能给出冻结我账号的明确理由，我非常怀疑你们的审核人员是否有种族歧视。\n请帮我解封账号，或者给出明确的冻结理由。\n谢谢\n到现在我都不知道我的账号为何被封，被举报还是被误杀？也许就像能哥说的，是马斯克在搬机房的时候弄坏了我的数据。看到能哥的说笑我还真可找到了可疑的证据: 账号被封之后几天，我发的推文里面的图片链接都挂掉了。\n不过在我收到的解封邮件里，推特还是告诉了我，被冻结是因为违反了平台的4条规则，我确信我都没有做过。以下是推特解封邮件提到的我违反的规则：\nSending multiple unsolicited @replies or mentions. Learn more. Posting multiple unrelated updates to a trending or popular topic. Learn more. Aggressive and random repost and/or liking posts from other accounts. Learn more. Misuse of X product features This includes any of the following:\n“follow churn” – following and then unfollowing large numbers of accounts in an effort to inflate one’s own follower count; indiscriminate following – following and/or unfollowing a large number of unrelated accounts in a short time period, particularly by automated means; and duplicating another account’s followers, particularly using automation. 时间能抚平一个人的伤痛，账号刚刚被封时，心里真不是滋味，莫名其妙的被这种大平台折腾，心里已经把这些大公司掌控的平台骂上几百遍，今天解封时，竟然没有对推特这个平台有任何埋怨了，只有开心。\n目前这个阶段我的账号对我很重要，我刚刚才开始 build in public，借助推特的全球化的平台来展示自己和自己的产品，没料到我和我的合伙人同事账号都被封了，我们的创业项目差点就失去了一个很重要的展示机会。\n不管怎样，再归来时，我的 AI 陪伴玩具产品已经卖到了世界十几个国家，感谢所有推友和购买我的产品支持我的朋友们，是你们在我账号消失的半年里给了我巨大帮助和鼓励，谢谢♥️。\n","permalink":"https://lewang.dev/posts/2024-01-31-my-twitter-unsuspended/","summary":"我的推特账号 x.com/lewangdev 终于解封了，从去年8月17号一直封到今天，已经差不多快有 6 个月的时间。在这 6 个月里，除了今年 1 月份，我每个月都写了一封申请解封的邮件，前几个月全部都是使用 ChatGPT 写的英文版解封申请，就在去年12月份的时候，想想自己莫名其妙被封确实有点恼火，于是用中文写了一封解封邮件，并且提到封我的账号是否是因为审核人员有种族歧视。以下是我的邮件内容：\nHi，\n已经过去四个月了，我的账号 lewangdev 还是被冻结着的，真是太难熬了。我翻看自己的推文，始终认为我没有发布任何破坏了推特的规则的推文，冻结我的账号一定是一个误会。\n我已经使用这个账号十多年了，一直在个人使用，我的账号被冻结不仅是我感觉到惊愕，我的所有朋友们都觉得太莫名奇妙了，让大家都觉得推特平台怎么会这样不确定。\n另外我不知道你们为什么会封禁我的账号，是因为我一个亚洲人吗，如果不能给出冻结我账号的明确理由，我非常怀疑你们的审核人员是否有种族歧视。\n请帮我解封账号，或者给出明确的冻结理由。\n谢谢\n到现在我都不知道我的账号为何被封，被举报还是被误杀？也许就像能哥说的，是马斯克在搬机房的时候弄坏了我的数据。看到能哥的说笑我还真可找到了可疑的证据: 账号被封之后几天，我发的推文里面的图片链接都挂掉了。\n不过在我收到的解封邮件里，推特还是告诉了我，被冻结是因为违反了平台的4条规则，我确信我都没有做过。以下是推特解封邮件提到的我违反的规则：\nSending multiple unsolicited @replies or mentions. Learn more. Posting multiple unrelated updates to a trending or popular topic. Learn more. Aggressive and random repost and/or liking posts from other accounts. Learn more. Misuse of X product features This includes any of the following:\n“follow churn” – following and then unfollowing large numbers of accounts in an effort to inflate one’s own follower count; indiscriminate following – following and/or unfollowing a large number of unrelated accounts in a short time period, particularly by automated means; and duplicating another account’s followers, particularly using automation.","title":"我的推特账号 lewangdev 终于解封了"},{"content":"两周前是我第一次去日本，而且还独自带了一个8岁的孩子一起，在日本十多天，算是佛系的走了几个地方，无论走到哪里都感觉基础设施非常好，城市干净，空气清新，物价低廉，社会文明程度极高，网络畅通，另外饮食也很适合我和孩子的口味，完全不懂日语也不会影响到旅行。\n在旅行的过程中，我使用最多的 App 是 Google Maps 和苹果自带的 Translate，找路和坐车都靠 Google Maps，Google Maps 唯一的不足是缺少店铺楼层的信息。我在上海很少用 Google Maps， 所以到了日本还不是很会用，有次在公交站台不知道自己有没有坐对方向来回看，也会有热心的人主动问我怎么样然后教我怎么用。\n另外苹果自带的 Translate 使用非常方便，可以语音也可以拍照进行中日文互译，这样在没有中英文菜单的饭馆，也能点菜了。我刚开始没有使用 Translate，第一天住酒店的时候，看到前台在用它与我沟通才想起这个自带的 App。\n接下来我从衣食住行四个方面说下我的体验。\n「衣」 这次游览的地方在东京和大阪之间的区域，体感温度和上海感觉差不多，只有在箱根大涌谷火山口的山顶比较冷。我们没有带很多衣服，穿着与在上海时完全相同。\n另外据说日本优衣库衣服非常便宜，打算实在不够穿再去买几件，后来路过优衣库的时候，还真的去买了几件衣服，但实在不知道要买什么，为了凑单到免税额，买了4件衣服。\n「食」 在日本旅行，最不担心就是吃饭。我们在上海也很喜欢吃日式的餐饮，这次去日本更是连续吃了7-8顿寿司。我没有提前去搜索网红的餐厅，基本都是在 Google Maps 上看看住的附近有什么，或者路过商场车站的时候，看看楼宇广告牌有什么吃的就过去了。寿司吃过连锁的店和自助餐厅，价格感觉都不高，人均1-200就能吃得很爽了，像京都站11楼的「築地寿司清」，70-80块就可以点一大盘了。\n另外我也去看了超市和便利店，价格低廉的便当比比皆是，而且还写清楚了热量和成分。\n感觉不足的地方就是蔬菜和水果吃得很少，这段时间基本是在酒店早餐里才吃得到，如香蕉橙子和蔬菜色拉。要么就是喝各种蔬菜汁和果汁。\n「住」 前面讲到日本物价低廉，也是因为我们体验了各种价格酒店之后得出来的一个结论。\n订酒店的时候，看到各种价格的酒店都有，这几天住的酒店价格分布在 100-1500 之间，我感觉 100-800 之间的酒店，体验下来基本没什么区别，房间小但都很干净，感觉就像吃的一样，什么价格都有，但是价格便宜的体验也是很好的，甚至几乎所有的酒店配置都差不多，都有浴缸卫洗丽净化器等。而且住的几家酒店的前台，大多都有会说英语的接待人员。\n我和朋友说，我在东京住了 300 多的酒店，在京都住了 100 多的酒店时，而且位置很好，酒店条件也很好，大多人反应都是日本酒店不会这么便宜，问我是不是青旅。等我发照片给他们看，才相信是真的。大概也因为是淡季，酒店整体的价格也便宜。\n「行」 从上海往返东京或者大阪的机票很便宜，我买的国行的航班往返，提早预订当时花了 6000 多，日本地震后，机票价格暴降，且因地震可以免费退票，于是我退掉重新预订了相同的航班，一下子省了 3000 多块。\n在日本的时候交通费用远高于上海，地铁公交都比上海贵一些。通过app和车站的牌子也能看到打车的价格，也是非常的高。 在日本坐地铁和新干线不需要安检，买新干线车票也不需要实名，这点非常的方便。\n我在到日本后，在 Apple Pay 里开通了 Suica，并且在东京站也给孩子办理了一张儿童版的 Suica 卡，这样小孩坐公交和地铁都可以半价。Suica 卡不仅可以当公交卡使用，在很多自动售货机和店铺都可以直接当银行卡使用，无接触的像刷公交卡一样付钱，相当的方便。\n下一次旅行 以前一直没有去过日本，因为担心语言的问题，现在来看，完全不用担心，这趟旅行我和孩子都非常满意，以后会多多去日本游玩。\n","permalink":"https://lewang.dev/posts/2024-01-30-my-first-japan-tourist/","summary":"两周前是我第一次去日本，而且还独自带了一个8岁的孩子一起，在日本十多天，算是佛系的走了几个地方，无论走到哪里都感觉基础设施非常好，城市干净，空气清新，物价低廉，社会文明程度极高，网络畅通，另外饮食也很适合我和孩子的口味，完全不懂日语也不会影响到旅行。\n在旅行的过程中，我使用最多的 App 是 Google Maps 和苹果自带的 Translate，找路和坐车都靠 Google Maps，Google Maps 唯一的不足是缺少店铺楼层的信息。我在上海很少用 Google Maps， 所以到了日本还不是很会用，有次在公交站台不知道自己有没有坐对方向来回看，也会有热心的人主动问我怎么样然后教我怎么用。\n另外苹果自带的 Translate 使用非常方便，可以语音也可以拍照进行中日文互译，这样在没有中英文菜单的饭馆，也能点菜了。我刚开始没有使用 Translate，第一天住酒店的时候，看到前台在用它与我沟通才想起这个自带的 App。\n接下来我从衣食住行四个方面说下我的体验。\n「衣」 这次游览的地方在东京和大阪之间的区域，体感温度和上海感觉差不多，只有在箱根大涌谷火山口的山顶比较冷。我们没有带很多衣服，穿着与在上海时完全相同。\n另外据说日本优衣库衣服非常便宜，打算实在不够穿再去买几件，后来路过优衣库的时候，还真的去买了几件衣服，但实在不知道要买什么，为了凑单到免税额，买了4件衣服。\n「食」 在日本旅行，最不担心就是吃饭。我们在上海也很喜欢吃日式的餐饮，这次去日本更是连续吃了7-8顿寿司。我没有提前去搜索网红的餐厅，基本都是在 Google Maps 上看看住的附近有什么，或者路过商场车站的时候，看看楼宇广告牌有什么吃的就过去了。寿司吃过连锁的店和自助餐厅，价格感觉都不高，人均1-200就能吃得很爽了，像京都站11楼的「築地寿司清」，70-80块就可以点一大盘了。\n另外我也去看了超市和便利店，价格低廉的便当比比皆是，而且还写清楚了热量和成分。\n感觉不足的地方就是蔬菜和水果吃得很少，这段时间基本是在酒店早餐里才吃得到，如香蕉橙子和蔬菜色拉。要么就是喝各种蔬菜汁和果汁。\n「住」 前面讲到日本物价低廉，也是因为我们体验了各种价格酒店之后得出来的一个结论。\n订酒店的时候，看到各种价格的酒店都有，这几天住的酒店价格分布在 100-1500 之间，我感觉 100-800 之间的酒店，体验下来基本没什么区别，房间小但都很干净，感觉就像吃的一样，什么价格都有，但是价格便宜的体验也是很好的，甚至几乎所有的酒店配置都差不多，都有浴缸卫洗丽净化器等。而且住的几家酒店的前台，大多都有会说英语的接待人员。\n我和朋友说，我在东京住了 300 多的酒店，在京都住了 100 多的酒店时，而且位置很好，酒店条件也很好，大多人反应都是日本酒店不会这么便宜，问我是不是青旅。等我发照片给他们看，才相信是真的。大概也因为是淡季，酒店整体的价格也便宜。\n「行」 从上海往返东京或者大阪的机票很便宜，我买的国行的航班往返，提早预订当时花了 6000 多，日本地震后，机票价格暴降，且因地震可以免费退票，于是我退掉重新预订了相同的航班，一下子省了 3000 多块。\n在日本的时候交通费用远高于上海，地铁公交都比上海贵一些。通过app和车站的牌子也能看到打车的价格，也是非常的高。 在日本坐地铁和新干线不需要安检，买新干线车票也不需要实名，这点非常的方便。\n我在到日本后，在 Apple Pay 里开通了 Suica，并且在东京站也给孩子办理了一张儿童版的 Suica 卡，这样小孩坐公交和地铁都可以半价。Suica 卡不仅可以当公交卡使用，在很多自动售货机和店铺都可以直接当银行卡使用，无接触的像刷公交卡一样付钱，相当的方便。\n下一次旅行 以前一直没有去过日本，因为担心语言的问题，现在来看，完全不用担心，这趟旅行我和孩子都非常满意，以后会多多去日本游玩。","title":"日本是一个完美的旅行地"},{"content":" 最近几天连续做了几次改图标的事情，非常无用却令我开心。\n给 Alacritty 换个图标 新安装了 Alacritty 这个终端，Alacritty 什么都好，就是图标我感觉太丑了。之前看到 @hitw93 的博客提到给 Alacritty 更改了图标，我照葫芦画瓢，做了红绿蓝三种颜色的终端图标，替换之后，每次在切换 app 或者在 Lanuchpad 页面看到它的图标时候，都会惊叹自己太有才了，心满意足。我制作的图标和替换方法都放在这里了：Alacritty.icns。\n另外 Alacritty 也支持了背景模糊，这个功能我等了很久，不用担心截图时候把背景也显示出来了。\n给输入法鼠须管 Squirrel 换个系统托盘图标 然后就是修改 rime 输入法鼠须管 Squirrel 的系统托盘图标了。macOS 在 Ventura/Monoma 系统之后，把输入法的图标由方形变成了矩形，在用 Ventura 或者 Monoma 选择输入法的时候，列表中那种不一致的感觉真让人难受，于是我去rime项目的仓库里搜索了一下有没有报过这个问题，果然有人与我的感受是相同的，我找到了修改图标的 PR，终于所有的输入法图标都一致变成矩形，看起来舒服了很多。\n替换步骤：\n打开目录 /Library/Input Methods/Squirrel.app/Contents/Resources 下载 rime.pdf 把目录下的 rime.pdf 换掉 退出系统登录再重新登录 给 LaCie 移动硬盘换个图标 今天又做了件没用的事：给移动硬盘换图标。买了块 SATA 口的 SSD，替换已经吃灰多年的 LaCie 移动硬盘里面的机械盘，替换完插到电脑上发现 LaCie 图标竟然没了，于是去官网搜了一下，不愧是法国的公司，管他性能怎么样，首先是要好看，所有产品的 icns 图标都可以下载，并且写清楚了怎么替换。图标和替换方法在这里：LaCie Drive Icons\n现在好了，电脑里看得见的地方都满意了，虽然没啥用，却能让自己快乐事，为何不去做。这可能是细节控，每一个做产品的人大概都很注重细节，但是细节在一些情况下也需要妥协。\n还是一件小事，我在发推特的时候会遇到：发推时是否需在英文单词的左右添加空格。我虽然有蓝标，但是我还是希望我的推文尽量只用一条并且能在 280 个字符以内，这样不会被折叠起来。如果提炼推文之后字数还超了，手动输入的空格明显就是浪费了，所以字数较少的推我会加空格，字数较多的推文我会去掉空格。\n就这样，我经常会折腾一些无用的事，想一些无用的问题，但是能给我带来快乐。\n","permalink":"https://lewang.dev/posts/2024-01-11-change-icons/","summary":"最近几天连续做了几次改图标的事情，非常无用却令我开心。\n给 Alacritty 换个图标 新安装了 Alacritty 这个终端，Alacritty 什么都好，就是图标我感觉太丑了。之前看到 @hitw93 的博客提到给 Alacritty 更改了图标，我照葫芦画瓢，做了红绿蓝三种颜色的终端图标，替换之后，每次在切换 app 或者在 Lanuchpad 页面看到它的图标时候，都会惊叹自己太有才了，心满意足。我制作的图标和替换方法都放在这里了：Alacritty.icns。\n另外 Alacritty 也支持了背景模糊，这个功能我等了很久，不用担心截图时候把背景也显示出来了。\n给输入法鼠须管 Squirrel 换个系统托盘图标 然后就是修改 rime 输入法鼠须管 Squirrel 的系统托盘图标了。macOS 在 Ventura/Monoma 系统之后，把输入法的图标由方形变成了矩形，在用 Ventura 或者 Monoma 选择输入法的时候，列表中那种不一致的感觉真让人难受，于是我去rime项目的仓库里搜索了一下有没有报过这个问题，果然有人与我的感受是相同的，我找到了修改图标的 PR，终于所有的输入法图标都一致变成矩形，看起来舒服了很多。\n替换步骤：\n打开目录 /Library/Input Methods/Squirrel.app/Contents/Resources 下载 rime.pdf 把目录下的 rime.pdf 换掉 退出系统登录再重新登录 给 LaCie 移动硬盘换个图标 今天又做了件没用的事：给移动硬盘换图标。买了块 SATA 口的 SSD，替换已经吃灰多年的 LaCie 移动硬盘里面的机械盘，替换完插到电脑上发现 LaCie 图标竟然没了，于是去官网搜了一下，不愧是法国的公司，管他性能怎么样，首先是要好看，所有产品的 icns 图标都可以下载，并且写清楚了怎么替换。图标和替换方法在这里：LaCie Drive Icons\n现在好了，电脑里看得见的地方都满意了，虽然没啥用，却能让自己快乐事，为何不去做。这可能是细节控，每一个做产品的人大概都很注重细节，但是细节在一些情况下也需要妥协。\n还是一件小事，我在发推特的时候会遇到：发推时是否需在英文单词的左右添加空格。我虽然有蓝标，但是我还是希望我的推文尽量只用一条并且能在 280 个字符以内，这样不会被折叠起来。如果提炼推文之后字数还超了，手动输入的空格明显就是浪费了，所以字数较少的推我会加空格，字数较多的推文我会去掉空格。\n就这样，我经常会折腾一些无用的事，想一些无用的问题，但是能给我带来快乐。","title":"无用却能让我快乐的小事——改图标"},{"content":"在主机还有 1 天到期之前，尝试下云就成功了，节省几千块。从阿里云下载的虚拟机文件在本地用 qemu 运行没有任何问题，因为备份的磁盘文件单个接近 200GB，我还担心网络问题以及操作复杂会比较耗时间，于是先把主机降到了最低配置准备再续一个月，没想到迁移过程很丝滑。这次迁移主机的系统盘和数据盘是通过下载从阿里云的磁盘快照构建镜像来恢复的，而不是通过备份数据的方式。\n通过直接恢复虚拟机磁盘这种方式来迁移云主机，相较于通过 rsync 备份数据，要方便得多。\n以下是我的操作过程。\n阿里云导出主机镜像的文档写得还是不错的，生成和导出磁盘镜像我都是看阿里云文档来操作。第一步是打开阿里云控制台进入镜像页面，为自己的云主机创建一个镜像。我的这台主机每天都自动生成快照，并且在创建镜像之前，我手动也创建了一个永久保存的快照，我用这个永久快照来构建镜像的。\n接下来点击「导出镜像」，阿里云后台会弹出提示窗口，根据提示配置好 oss Bucket，然后耐心等待，最后在会在 oss Bucket 看到两个文件，一个是系统盘，另外一个数据盘。\n从 oss Bucket 下载文件到外网的流量是收费，我第一次直接用 http 链接下载，系统盘可以下载下来，但是数据盘下载失败了，浪费了不少的流量（钱），后来尝试用 阿里云的 ossutil 命令来下载，一次就成功了，把着两个文件下载到本地之后，接下来在本地使用 qemu 就可以把虚拟机跑起来\n我从阿里云导出的 raw 格式的镜像， qemu 可以直接使用，如果打算用 virtualbox 来运行，也可以尝试用 qumu-img 转为 virtualbox 支持的磁盘格式。我这里直接解压系统盘和数据盘文件之后，执行一下命令来构建虚拟机：\n# 使用系统盘创建虚拟机 virt-install \\ --name aliyun-ci \\ --osinfo centos7 \\ --ram 8192 \\ --vcpus 4 \\ --network network=default \\ --disk /var/lib/libvirt/images/aliyun-ci.raw \\ --graphics vnc,listen=0.0.0.0 \\ --noautoconsole --autostart \\ --import # 挂载数据盘 virsh attach-disk aliyun-ci /mnt/nas/aliyun/ci/data.raw vdb 我从阿里云备份到我本地电脑的磁盘文件是 raw 格式，如果忘记了导出的阿里云主机的 root 密码，可以直接使用 virt-edit 命令进行修改：\nvirt-edit system.raw /etc/shadow 将文件中加密后的字符串删除，虚拟机开机后 root 用户不需要密码就可以直接登录，登录后再修改一下密码就行了。\n进入启动之后虚拟机，修改一下网络配置，另外我还安装配置了 WireGuard，把这台导出的虚拟机连到了我的阿里云内网中，这样虚拟机中服务几乎不用修改，就恢复正常访问了。\n","permalink":"https://lewang.dev/posts/2023-12-04-bails-out-of-the-public-cloud/","summary":"在主机还有 1 天到期之前，尝试下云就成功了，节省几千块。从阿里云下载的虚拟机文件在本地用 qemu 运行没有任何问题，因为备份的磁盘文件单个接近 200GB，我还担心网络问题以及操作复杂会比较耗时间，于是先把主机降到了最低配置准备再续一个月，没想到迁移过程很丝滑。这次迁移主机的系统盘和数据盘是通过下载从阿里云的磁盘快照构建镜像来恢复的，而不是通过备份数据的方式。\n通过直接恢复虚拟机磁盘这种方式来迁移云主机，相较于通过 rsync 备份数据，要方便得多。\n以下是我的操作过程。\n阿里云导出主机镜像的文档写得还是不错的，生成和导出磁盘镜像我都是看阿里云文档来操作。第一步是打开阿里云控制台进入镜像页面，为自己的云主机创建一个镜像。我的这台主机每天都自动生成快照，并且在创建镜像之前，我手动也创建了一个永久保存的快照，我用这个永久快照来构建镜像的。\n接下来点击「导出镜像」，阿里云后台会弹出提示窗口，根据提示配置好 oss Bucket，然后耐心等待，最后在会在 oss Bucket 看到两个文件，一个是系统盘，另外一个数据盘。\n从 oss Bucket 下载文件到外网的流量是收费，我第一次直接用 http 链接下载，系统盘可以下载下来，但是数据盘下载失败了，浪费了不少的流量（钱），后来尝试用 阿里云的 ossutil 命令来下载，一次就成功了，把着两个文件下载到本地之后，接下来在本地使用 qemu 就可以把虚拟机跑起来\n我从阿里云导出的 raw 格式的镜像， qemu 可以直接使用，如果打算用 virtualbox 来运行，也可以尝试用 qumu-img 转为 virtualbox 支持的磁盘格式。我这里直接解压系统盘和数据盘文件之后，执行一下命令来构建虚拟机：\n# 使用系统盘创建虚拟机 virt-install \\ --name aliyun-ci \\ --osinfo centos7 \\ --ram 8192 \\ --vcpus 4 \\ --network network=default \\ --disk /var/lib/libvirt/images/aliyun-ci.raw \\ --graphics vnc,listen=0.0.0.0 \\ --noautoconsole --autostart \\ --import # 挂载数据盘 virsh attach-disk aliyun-ci /mnt/nas/aliyun/ci/data.","title":"一次完美的逃离公有云操作：把挂载300G数据盘的阿里云主机迁移到本地"},{"content":"昨天组装了一台GPU 开发机，我使用 rcode+vscode+docker 可以快速搭建出隔离的cuda开发环境，我感觉非常的方便。特别是 rcode 这个命令在使用 docker 构建 cuda 开发环境时非常有用。下面以搭建 faster-whisper 开发环境为例：\nfaster-whisper 的项目说明里提到，NVIDIA 官方 Docker 镜像 nvidia/cuda:11.8.0-cudnn8-runtime-ubuntu22.04 已经包括了开发需要的库 cuBLAS for CUDA 11 和 cuDNN 8 for CUDA 11。执行以下命令下载并启动这个镜像，并且在启动时，安装好需要的 python 库 和 openssh-server 服务\n执行以下命令查找到刚刚启动的容器的名称\nsudo docker ps 例如我的容器名称是 admiring_kapitsa，使用以下命令进入容器的 console\nsudo docker exec -it admiring_kapitsa /bin/bash 进入 console 之后，进入 /root 目录，创建以下目录和文件，并且把自己的公钥加到 authorized_keys 中：\nmkdir /root/.ssh touch /root/.ssh/authorized_keys chmod 600 /root/.ssh/authorized_keys 经过这一步操作之后，后面可以通过 ssh 直接登录到容器中。\n关闭容器 admiring_kapitsa 之后，把容器 commit 为一个本地镜像，这样以后可以重复使用\nsudo docker stop admiring_kapitsa sudo docker commit admiring_kapitsa lewangdev/cudalab 下载 faster-whisper 的代码然后重新启动一个容器，并且配置好容器的 ssh 端口，挂载 faster-whisper 项目代码到容器中，例如我这里把 8822 端口映射到容器的 22 端口，把 faster-whisper 代码挂载到容器的 /src 目录下：\nsudo docker run --runtime=nvidia --gpus all -p 8822:22 -v ./faster-whisper:/src lewangdev/cudalab /bin/bash -c \\ \u0026#34;service ssh start; tail -f\u0026#34; 修改自己的电脑的 ~/.ssh/config 文件，例如我的开发机的 ip 是 192.168.52.45，则可以添加以下内容：\nHost cudalab HostName 192.168.52.45 User root Port 8822 添加好之后，就可以直接这样来登录到容器中。\nssh cudalab 或者\nssh root@192.168.52.45 -p 8822 完成前面的步骤之后，使用 rcode 命令就可以直接用 vscode 打开开发目录\nrcode cudalab /src ","permalink":"https://lewang.dev/posts/2023-11-28-build-cuda-dev-env-with-rcode-vscode-docker/","summary":"昨天组装了一台GPU 开发机，我使用 rcode+vscode+docker 可以快速搭建出隔离的cuda开发环境，我感觉非常的方便。特别是 rcode 这个命令在使用 docker 构建 cuda 开发环境时非常有用。下面以搭建 faster-whisper 开发环境为例：\nfaster-whisper 的项目说明里提到，NVIDIA 官方 Docker 镜像 nvidia/cuda:11.8.0-cudnn8-runtime-ubuntu22.04 已经包括了开发需要的库 cuBLAS for CUDA 11 和 cuDNN 8 for CUDA 11。执行以下命令下载并启动这个镜像，并且在启动时，安装好需要的 python 库 和 openssh-server 服务\n执行以下命令查找到刚刚启动的容器的名称\nsudo docker ps 例如我的容器名称是 admiring_kapitsa，使用以下命令进入容器的 console\nsudo docker exec -it admiring_kapitsa /bin/bash 进入 console 之后，进入 /root 目录，创建以下目录和文件，并且把自己的公钥加到 authorized_keys 中：\nmkdir /root/.ssh touch /root/.ssh/authorized_keys chmod 600 /root/.ssh/authorized_keys 经过这一步操作之后，后面可以通过 ssh 直接登录到容器中。\n关闭容器 admiring_kapitsa 之后，把容器 commit 为一个本地镜像，这样以后可以重复使用\nsudo docker stop admiring_kapitsa sudo docker commit admiring_kapitsa lewangdev/cudalab 下载 faster-whisper 的代码然后重新启动一个容器，并且配置好容器的 ssh 端口，挂载 faster-whisper 项目代码到容器中，例如我这里把 8822 端口映射到容器的 22 端口，把 faster-whisper 代码挂载到容器的 /src 目录下：","title":"使用 rcode+vscode+docker 快速搭建隔离的 cuda 开发环境"},{"content":" 获取「改造火火兔」动态和「FoloToy」玩具进展请关注我的电报频道 FoloToy\n从上个月在推上公开一系列脑洞开始，到选定其中做智能硬件的想法，借助「改造火火兔」到昨天大概一个多月才算验证通所有的产品化技术难点，群友们都在安静等待着产品发布，我自己反而觉得越来越着急，这是一个月遇到技术问题的汇总，希望也能给其他做 IoT 硬件产品的朋友以启发。\n我们没有选用树莓派之类的可以运行完整 Linux 的方案，而是采用了 ESP32 嵌入式方案，我们希望模块可以做得非常小，功耗低，成本也低，这样我们就需要自己搞定硬件的设计和固件的开发，也就遇到了后续的这些问题。\n开始的选择了 ESP32S3 模块，搭配数字麦克风，第一版硬件做出来以后，发现数字麦克风在 USB 充电时总是会受到干扰，导致录音噪声太多以致不能识别语音，经过多次尝试，始终无法解决问题，我认为这种缺陷无法接受，需要调整。在测试玩具自带的模拟麦克风时，发现效果非常好，于是尝试使用自带麦克风。\n起初想法是用户在拿到板子后，不需要自己运行服务程序，只需在固件中配置好 OpenAI 和 Azure 的 APIKey 就可以使用，在做了一些测试之后，发现芯片计算能力有限，从录音到播放生成的回复语音这段过程耗时太长，这样的体验让人难以接受，而把语音处理的过程放到服务器来实现后，回复较短的情况下，整体的响应的速度提高到 3-5s。\nChatGPT 玩具在对话时需要一直在线，是需要联网的 IoT 设备，除了要支持上下行控制指令和配置参数的下发上传，还需要上传下载语音。实现上，在线控制我们使用了 MQTT， 实时发送语音使用单独了 UDP 通道，设备再通过 HTTPS 下载服务器生成好的语音回复文件来播放，数据传输的方案也是遇到一些问题后才确定的。\n一开始，我们认为玩具的交互过程非常简单，直接使用 UDP 就可以了，但在实现的过程中发现，服务器在向 ESP32 板子发送 UDP 的语音数据时，会发生非常明显的丢包，从而造成扬声器播放有断断续续的现象，而且不同的电脑运行服务器程序现象还不尽相同，我的 MacBook 体验就很好，而换成 ThinkPad 就丢包严重。\n从 UDP 上解决这个问题我感觉难度较大，可能要我们自己做缓存适配硬件解码速度的来播放语音。于是我们从服务器推语音文件转向由板子自己拉(下载)语音文件的方式，流程：板子通过 UDP 上传语音，服务器接收语音后通过 MQTT 下发语音文件 URL，在服务器完成所有的接口调用和语音处理之后，通知板子下载语音。\n到此，技术上主要的问题都已经解决，此外我们还做好了通过 Web 更新固件，配网，定义好了灯的状态，而且芯片换为资源更多的 ESP32，从而有足够的 Flash 用于存储出厂固件等。\n另外也我注册好了产品的域名 https://FoloToy.com，接下来我们在 https://FoloToy.com 上相会, 谢谢。\n","permalink":"https://lewang.dev/posts/2023-08-29-from-huohuotu-to-folotoy/","summary":"获取「改造火火兔」动态和「FoloToy」玩具进展请关注我的电报频道 FoloToy\n从上个月在推上公开一系列脑洞开始，到选定其中做智能硬件的想法，借助「改造火火兔」到昨天大概一个多月才算验证通所有的产品化技术难点，群友们都在安静等待着产品发布，我自己反而觉得越来越着急，这是一个月遇到技术问题的汇总，希望也能给其他做 IoT 硬件产品的朋友以启发。\n我们没有选用树莓派之类的可以运行完整 Linux 的方案，而是采用了 ESP32 嵌入式方案，我们希望模块可以做得非常小，功耗低，成本也低，这样我们就需要自己搞定硬件的设计和固件的开发，也就遇到了后续的这些问题。\n开始的选择了 ESP32S3 模块，搭配数字麦克风，第一版硬件做出来以后，发现数字麦克风在 USB 充电时总是会受到干扰，导致录音噪声太多以致不能识别语音，经过多次尝试，始终无法解决问题，我认为这种缺陷无法接受，需要调整。在测试玩具自带的模拟麦克风时，发现效果非常好，于是尝试使用自带麦克风。\n起初想法是用户在拿到板子后，不需要自己运行服务程序，只需在固件中配置好 OpenAI 和 Azure 的 APIKey 就可以使用，在做了一些测试之后，发现芯片计算能力有限，从录音到播放生成的回复语音这段过程耗时太长，这样的体验让人难以接受，而把语音处理的过程放到服务器来实现后，回复较短的情况下，整体的响应的速度提高到 3-5s。\nChatGPT 玩具在对话时需要一直在线，是需要联网的 IoT 设备，除了要支持上下行控制指令和配置参数的下发上传，还需要上传下载语音。实现上，在线控制我们使用了 MQTT， 实时发送语音使用单独了 UDP 通道，设备再通过 HTTPS 下载服务器生成好的语音回复文件来播放，数据传输的方案也是遇到一些问题后才确定的。\n一开始，我们认为玩具的交互过程非常简单，直接使用 UDP 就可以了，但在实现的过程中发现，服务器在向 ESP32 板子发送 UDP 的语音数据时，会发生非常明显的丢包，从而造成扬声器播放有断断续续的现象，而且不同的电脑运行服务器程序现象还不尽相同，我的 MacBook 体验就很好，而换成 ThinkPad 就丢包严重。\n从 UDP 上解决这个问题我感觉难度较大，可能要我们自己做缓存适配硬件解码速度的来播放语音。于是我们从服务器推语音文件转向由板子自己拉(下载)语音文件的方式，流程：板子通过 UDP 上传语音，服务器接收语音后通过 MQTT 下发语音文件 URL，在服务器完成所有的接口调用和语音处理之后，通知板子下载语音。\n到此，技术上主要的问题都已经解决，此外我们还做好了通过 Web 更新固件，配网，定义好了灯的状态，而且芯片换为资源更多的 ESP32，从而有足够的 Flash 用于存储出厂固件等。\n另外也我注册好了产品的域名 https://FoloToy.com，接下来我们在 https://FoloToy.com 上相会, 谢谢。","title":"从「改造火火兔」开始，到遇见 ChatGPT 玩具「FoloToy」"},{"content":"上个月在 X 上公开脑洞之后，得到了很多 X 友的鼓励和帮助，而且讨论氛围实在是太棒，让我很是感动。看到有位 X 友这样说：\n𝕏𝕏助手™：评论区的态度和语气都很友善，让思路在探讨中变得更清晰这件事本身，不光对推主，对其他参与讨论或仅围观的推友来说，或许都有一定的参考价值。也是交流的意义之所在\n原文点击这里查看：原文。\n我把一些建议汇总了一下，放在这篇博文里。\n开发硬件产品的门槛，做方案和做产品的区别 象牙山刘能：儿童那个不错，不过看起来不像是单枪匹马的项目\n小码哥：能哥看得很准，我有个小团队，做 IoT 方案的，在验证这个想法\n雷尼尔： 这个方案技术上的问题不是问题，我验证过了。但是真正的门槛在后面，产品开发和市场运营。 因为这个东西需要一直联网，而且对网络的信号有要求。另外设备网络的初始化，以及chatgpt或者类似技术在大陆的合规，都是比较大的问题。另外就是moq。你这个产品出来后，产品是自己做还是卖给其他人，差别很大。如果你自己做，开模具，到产品的初次量产，需要投入的资金都是不低的。做硬件产品的门槛要比软件高很多。\n小码哥：合规的问题在大陆比较难解决，等政策明确了再看走正常途径申报，或者面向国内的产品就用国内的技术方案。光做硬件做到“产品”级门槛就很高，外型做好看一点，硬件本体最大的成本可能就是结构设计和开模。\n我考虑先不追求外观，尽可能利用现有玩具的公模或者3d打印，芯片方案考虑用 ESP32，先做 DEMO\norange.ai：ai智能硬件空间还蛮大的，找到合适的模型，效果会很好。国家对硬件也没有啥监管，挺好发育的。\nhenu王凯：硬件门槛特别高、周期长、风险大，独立开发者、资金不宽裕的别碰硬件。\nJustin.Ko: 智能硬件能toc，做得好能上量。对应的问题是团队变大，链条长，对创业有一定难度\nMorris： 为什么用火火兔做智能硬件Demo？它有开放接口可定制吗？\n小码哥: 因为我家里刚好有，另外它也是国内前几年儿童故事机市场最顶级的IP，应该会比较容易玩起来\nZHX：儿童玩具硬件部分也容易实现，主要是营销推广\n昵称太受欢迎已被抢注: 儿童教育，英语智能对话，或者发音纠正的，应该很好\n如何去做 Digital Signage（数字广告屏） Raymond: 我不是做硬件的，但是我其实非常羡慕可以做硬件的朋友，说实话，需求真的是太多了，比卷上天的软件多了去了。独立开发者，成功的案例也多，比如做 HHKB 蓝牙硬件的那个人，这个算是非常细分，非常小众了吧，过去10年，他个人只是做这个的收益，肯定非常可观了。还有哪个做LED氛围灯的，也是做的不错。\nYup: 这些里面我最看好的项目是做 Digital Signage的 SaaS，这个市场不大，但毛利丰足。做一个通用的管理后端，以插件形式深耕各个使用场景，以至于插件市场也是可以做成收费。将你们很在行的硬件盒子铺到Amazon等渠道，找KOL review一下，在线广告推一推，可以的。。。\nYup: 另外，做SaaS就真的不建议面向国内市场了，一定要先出海\n小码哥: 多谢指导。前段时间接了一个港澳那边 Digital Signage 的项目，做完项目之后，大体上是这个思路\nBobJiang: digital signage SaaS这个有利润，虽然规模不大，但是做深一点，复购还是可以。具体就考验你怎么执行了\n小码哥: 确实，最近做了一个海外的digital signage项目，都是采购的软硬件产品来交付的，看到里面有不少地方可以去改进，关键的是项目的利润非常好。\n验证产品需求的方法 Luo说不啰嗦: 加油！建议先验证需求再开动，选择比努力重要。\nTantan Fu: 请教一下如何验证需求呀？ 你看我的尝试，感觉验证不成功\nLuo说不啰嗦: 首先，想清楚你的用户是谁，这个画像一定要越精准越好，不要试图一上来做一款 for everyone 的产品，这会成为你的负担。想清楚用户是谁之后，把人群特征用几个关键词列出来，这时候你就可以开始验证了：落地页/博客/社群/发帖, 尽可能多地收集线索 (leads)，和他们对话，做预售。\nLuo说不啰嗦: 如果用户反应很强烈，甚至愿意为你的 idea 付费，这时候就可以认为你完成了想法验证。需要指出的是，要分清用户的真实兴趣和恭维，如果有时间，可以读一读 The Mom Test 这本书，相信读完你会有新的收获。\n产品开发的误区——从技术能力出发而不是从需求出发 Chang Wei 昌维：加油。我认为做独立开发者最重要的反倒不是技术本身，而是挖掘需求和用户增长，产品设计\u0026gt;技术实现\nbiger：程序员思维误区，东西都很牛逼，感觉也能做的很牛逼，可是没考虑到大众化的实际需求和推广问题，变现感觉还是有点难\n小码哥: 确实如此，技术人员总是站在技术能力和供应端有思考，消费端几乎不了解，我们一直都有这样问题，所以想多听听推友们的意见\n刘江/LIU Jiang：不要只考虑能力、供给侧，要再看一下需求侧和竞争，后者可能是更重要的事情\nFenng：冷静。不要被看客忽悠。不客气的说，都是一些初级的点子。\nJustin.Ko：把这个topic放出来讨论感觉不错，说点个人的看法。总体上感觉偏技术（我个人是做算法的～），做产品的话还要更看下市场需求，付费意愿（是不是解决痛点，或者能给人省钱）。儿童教育类的确实是一个比较刚需的市场，进入这个行业具体做产业链中的哪一个环节可以考察下，另外软硬产品复杂度远大于纯软件\n创业需要家人的支持 Michael Anti：一句话，老婆伟大。\nWinchman🇭🇰852Web3：完全能理解你的情況，事在人為，路在腳邊，有像嫂子一樣的隊友，家事能解決就算是把最難的問題解決了，那麼一切問題都能解決\n杰瑞瑞：碰到这种情况都不敢和老婆说…\nRains：论有一个明智老婆的优势\n小码哥：无论创业还是做独立开发者，都是需要家人的支持。\n评论区都是精华 感兴趣的可以再点击去看看：原文。\n","permalink":"https://lewang.dev/posts/2023-08-17-comments-on-x-about-my-startup-ideas/","summary":"上个月在 X 上公开脑洞之后，得到了很多 X 友的鼓励和帮助，而且讨论氛围实在是太棒，让我很是感动。看到有位 X 友这样说：\n𝕏𝕏助手™：评论区的态度和语气都很友善，让思路在探讨中变得更清晰这件事本身，不光对推主，对其他参与讨论或仅围观的推友来说，或许都有一定的参考价值。也是交流的意义之所在\n原文点击这里查看：原文。\n我把一些建议汇总了一下，放在这篇博文里。\n开发硬件产品的门槛，做方案和做产品的区别 象牙山刘能：儿童那个不错，不过看起来不像是单枪匹马的项目\n小码哥：能哥看得很准，我有个小团队，做 IoT 方案的，在验证这个想法\n雷尼尔： 这个方案技术上的问题不是问题，我验证过了。但是真正的门槛在后面，产品开发和市场运营。 因为这个东西需要一直联网，而且对网络的信号有要求。另外设备网络的初始化，以及chatgpt或者类似技术在大陆的合规，都是比较大的问题。另外就是moq。你这个产品出来后，产品是自己做还是卖给其他人，差别很大。如果你自己做，开模具，到产品的初次量产，需要投入的资金都是不低的。做硬件产品的门槛要比软件高很多。\n小码哥：合规的问题在大陆比较难解决，等政策明确了再看走正常途径申报，或者面向国内的产品就用国内的技术方案。光做硬件做到“产品”级门槛就很高，外型做好看一点，硬件本体最大的成本可能就是结构设计和开模。\n我考虑先不追求外观，尽可能利用现有玩具的公模或者3d打印，芯片方案考虑用 ESP32，先做 DEMO\norange.ai：ai智能硬件空间还蛮大的，找到合适的模型，效果会很好。国家对硬件也没有啥监管，挺好发育的。\nhenu王凯：硬件门槛特别高、周期长、风险大，独立开发者、资金不宽裕的别碰硬件。\nJustin.Ko: 智能硬件能toc，做得好能上量。对应的问题是团队变大，链条长，对创业有一定难度\nMorris： 为什么用火火兔做智能硬件Demo？它有开放接口可定制吗？\n小码哥: 因为我家里刚好有，另外它也是国内前几年儿童故事机市场最顶级的IP，应该会比较容易玩起来\nZHX：儿童玩具硬件部分也容易实现，主要是营销推广\n昵称太受欢迎已被抢注: 儿童教育，英语智能对话，或者发音纠正的，应该很好\n如何去做 Digital Signage（数字广告屏） Raymond: 我不是做硬件的，但是我其实非常羡慕可以做硬件的朋友，说实话，需求真的是太多了，比卷上天的软件多了去了。独立开发者，成功的案例也多，比如做 HHKB 蓝牙硬件的那个人，这个算是非常细分，非常小众了吧，过去10年，他个人只是做这个的收益，肯定非常可观了。还有哪个做LED氛围灯的，也是做的不错。\nYup: 这些里面我最看好的项目是做 Digital Signage的 SaaS，这个市场不大，但毛利丰足。做一个通用的管理后端，以插件形式深耕各个使用场景，以至于插件市场也是可以做成收费。将你们很在行的硬件盒子铺到Amazon等渠道，找KOL review一下，在线广告推一推，可以的。。。\nYup: 另外，做SaaS就真的不建议面向国内市场了，一定要先出海\n小码哥: 多谢指导。前段时间接了一个港澳那边 Digital Signage 的项目，做完项目之后，大体上是这个思路\nBobJiang: digital signage SaaS这个有利润，虽然规模不大，但是做深一点，复购还是可以。具体就考验你怎么执行了\n小码哥: 确实，最近做了一个海外的digital signage项目，都是采购的软硬件产品来交付的，看到里面有不少地方可以去改进，关键的是项目的利润非常好。\n验证产品需求的方法 Luo说不啰嗦: 加油！建议先验证需求再开动，选择比努力重要。\nTantan Fu: 请教一下如何验证需求呀？ 你看我的尝试，感觉验证不成功\nLuo说不啰嗦: 首先，想清楚你的用户是谁，这个画像一定要越精准越好，不要试图一上来做一款 for everyone 的产品，这会成为你的负担。想清楚用户是谁之后，把人群特征用几个关键词列出来，这时候你就可以开始验证了：落地页/博客/社群/发帖, 尽可能多地收集线索 (leads)，和他们对话，做预售。","title":"公开“脑洞”之后X友们的建议精选"},{"content":" 写了篇随笔记录送娃那天的心里感受，拍照片录视频很容易记录一些画面，但是想把自己的心里感受的记录下来，可能还是要靠文字\n我的妹妹一家三口住在南方小镇，暑假之前儿子就和我说好，等放假了他要和奶奶一起去姑姑家找哥哥玩。上海南站有趟卧铺列车时间很合适，在高铁的时代，儿子从来没有体验过卧铺列车旅行，全家商量好之后，儿子决定尝试“新”的旅行方式。\n送儿子和我妈去上海南站坐车，我先送他们到出发层，车站门口只能停 6 分钟，送他们到进站口后，我便马上返回车上开往停车场，准备在停好车之后再去车站找他们，还没等我停好车，儿子打来电话，好稚嫩的声音：“爸爸，你走吧，我们不需要你了，我们已经找到了候车的地方。”\n我听完在夸他做得很好之后，除了惊讶，心里竟有了些失落，不过想到他那小小孩子就有了独立自信的样子，我心里也有了些许难以言说的自豪。\n我刚出停车场没开出多远，我妈又打来了电话，说她在候车厅看到有人可以帮他们送上车，问我要不要让人送一下。我当然说同意，然后就听到我妈在电话里传来喊人的声音：“师傅，师傅，帮我们送上去” 。挂掉电话后，我继续驾车前行。\n不一会儿，我正在高架上行驶，我妈再一次打来了电话。她告诉我他们已经到达了自己的卧铺座位，还告诉我他们没有找人送，因为我儿子不同意，死活不让别人送，坚持要自己去找位置。\n其实在我妈打电话给我的时候，我也在想，虽然这是你们第一次坐卧铺车，但也没什么行李，是可以自己排队检票上车的。这时才明白原来我妈给我打电话是希望征得我的支持来否定她孙子的想法。\n儿子继续在电话里和我说，奶奶路上一直在问人，他觉得没有必要，因为他有妈妈给他制作的乘车卡片，再根据车站里面的路牌指示，就可以认得路。我能想象出儿子他那坚定自信的样子，就像平时他坚持他可以自己建造他的乐高城堡一样。\n祝他们旅途顺利。\n","permalink":"https://lewang.dev/posts/2023-07-08-i-dont-need-you-any-more/","summary":"写了篇随笔记录送娃那天的心里感受，拍照片录视频很容易记录一些画面，但是想把自己的心里感受的记录下来，可能还是要靠文字\n我的妹妹一家三口住在南方小镇，暑假之前儿子就和我说好，等放假了他要和奶奶一起去姑姑家找哥哥玩。上海南站有趟卧铺列车时间很合适，在高铁的时代，儿子从来没有体验过卧铺列车旅行，全家商量好之后，儿子决定尝试“新”的旅行方式。\n送儿子和我妈去上海南站坐车，我先送他们到出发层，车站门口只能停 6 分钟，送他们到进站口后，我便马上返回车上开往停车场，准备在停好车之后再去车站找他们，还没等我停好车，儿子打来电话，好稚嫩的声音：“爸爸，你走吧，我们不需要你了，我们已经找到了候车的地方。”\n我听完在夸他做得很好之后，除了惊讶，心里竟有了些失落，不过想到他那小小孩子就有了独立自信的样子，我心里也有了些许难以言说的自豪。\n我刚出停车场没开出多远，我妈又打来了电话，说她在候车厅看到有人可以帮他们送上车，问我要不要让人送一下。我当然说同意，然后就听到我妈在电话里传来喊人的声音：“师傅，师傅，帮我们送上去” 。挂掉电话后，我继续驾车前行。\n不一会儿，我正在高架上行驶，我妈再一次打来了电话。她告诉我他们已经到达了自己的卧铺座位，还告诉我他们没有找人送，因为我儿子不同意，死活不让别人送，坚持要自己去找位置。\n其实在我妈打电话给我的时候，我也在想，虽然这是你们第一次坐卧铺车，但也没什么行李，是可以自己排队检票上车的。这时才明白原来我妈给我打电话是希望征得我的支持来否定她孙子的想法。\n儿子继续在电话里和我说，奶奶路上一直在问人，他觉得没有必要，因为他有妈妈给他制作的乘车卡片，再根据车站里面的路牌指示，就可以认得路。我能想象出儿子他那坚定自信的样子，就像平时他坚持他可以自己建造他的乐高城堡一样。\n祝他们旅途顺利。","title":"逐渐消失的被依靠感"},{"content":" 这几天在整理自己以前随意记录的文字,准备喂给 ChatGPT 看看能不能做出一个 AI, 真的 Talk to Myself, 顺便放几篇到博客上来充充数\n本文写于 2018 年 8 月 28 日, 天气晴 28-33°C\n这几天上海的天气都很不错，最近一个月台风特别多，而且巧得很都是周末来。最热的夏天已经过去了，早上骑着共享单车去云台路那边的星巴克办公，大概需要骑行 15 分钟。\n每天骑车路上，总是有汽车不让路权的情形发生，没办法，肉身是斗不过杀人机器的，只好让让他们，人们的驾驶素养如此的差劲，没有好转的迹象，越来越让自己失去了对这个中国最发达城市一点点的爱。像这样的情形，几乎走在马路的人天天都会遇到，也没人愿意出来指责，姑且就这样吧。\n想起来前几天在黄浦区人民政府那栋棺材楼附近，我绿灯走人行道过马路，还被转弯的烂司机指着警告，连一旁的金发外国友人都看了瞠目结舌。本想跟他较真到底，想想还是算了，祖国的人民警察那么忙，没空来处理这样的小屁事。就算来了又如何，对司机扣 2 分罚 100 块钱能有什么用？更何况，满大街的都是没有执法权的临时工警察(辅警协警), 喊了他们也没用, 就算喊来了，他们也是会催你赶紧走，别妨碍交通。哪里还有希望，我看不到。\n不想太烦心，随他去吧，爱怎样就怎样，没人在乎。\n每天的最快乐的时光是下班回到家里。和老婆一起陪陪孩子，做做游戏，聊聊天，轻松自在。今天和娃一起玩识字的游戏，一人写，家里剩下的人认字。儿子才三岁，当然不太认识几个汉字，但是他有很强的要“赢”的心态，一旦“输”就会伤心的哭起来。我妈说很像我小时候，自尊心太强了。\n我不太认同自尊心太强这个说法。好在自己小时的那种感觉还没有完全消失，希望能帮助儿子自己找到问题（也可能不是问题），要让他知道，输了也无妨，至少可以看到自己的弱在哪里，而且能认识冠军赢家也挺好，输了也可以大方的走过去为赢家送去祝贺。更何况，很多事情根本也不是比赛，不一定要有输赢。\n","permalink":"https://lewang.dev/posts/2023-03-20-thinking-daily-1/","summary":"这几天在整理自己以前随意记录的文字,准备喂给 ChatGPT 看看能不能做出一个 AI, 真的 Talk to Myself, 顺便放几篇到博客上来充充数\n本文写于 2018 年 8 月 28 日, 天气晴 28-33°C\n这几天上海的天气都很不错，最近一个月台风特别多，而且巧得很都是周末来。最热的夏天已经过去了，早上骑着共享单车去云台路那边的星巴克办公，大概需要骑行 15 分钟。\n每天骑车路上，总是有汽车不让路权的情形发生，没办法，肉身是斗不过杀人机器的，只好让让他们，人们的驾驶素养如此的差劲，没有好转的迹象，越来越让自己失去了对这个中国最发达城市一点点的爱。像这样的情形，几乎走在马路的人天天都会遇到，也没人愿意出来指责，姑且就这样吧。\n想起来前几天在黄浦区人民政府那栋棺材楼附近，我绿灯走人行道过马路，还被转弯的烂司机指着警告，连一旁的金发外国友人都看了瞠目结舌。本想跟他较真到底，想想还是算了，祖国的人民警察那么忙，没空来处理这样的小屁事。就算来了又如何，对司机扣 2 分罚 100 块钱能有什么用？更何况，满大街的都是没有执法权的临时工警察(辅警协警), 喊了他们也没用, 就算喊来了，他们也是会催你赶紧走，别妨碍交通。哪里还有希望，我看不到。\n不想太烦心，随他去吧，爱怎样就怎样，没人在乎。\n每天的最快乐的时光是下班回到家里。和老婆一起陪陪孩子，做做游戏，聊聊天，轻松自在。今天和娃一起玩识字的游戏，一人写，家里剩下的人认字。儿子才三岁，当然不太认识几个汉字，但是他有很强的要“赢”的心态，一旦“输”就会伤心的哭起来。我妈说很像我小时候，自尊心太强了。\n我不太认同自尊心太强这个说法。好在自己小时的那种感觉还没有完全消失，希望能帮助儿子自己找到问题（也可能不是问题），要让他知道，输了也无妨，至少可以看到自己的弱在哪里，而且能认识冠军赢家也挺好，输了也可以大方的走过去为赢家送去祝贺。更何况，很多事情根本也不是比赛，不一定要有输赢。","title":"日常吐槽"},{"content":"前几天亚马逊宣告 Kindle 中国电子书店运营进行调整，通知标题写得比较委婉，其实就是亚马逊电子书即将退出中国市场，到时候我们购买的国行版 Kindle 将不能购买新电子书，也不能下载已经购买的电子书了。\n如果购买的电子书比较多，手动一本一本的去下载是挺费时间的，我来分享一下我是如何使用 Python 一键下载所有电子书的。\n安装 Python 用 Chrome 浏览器打开 https://python.org，下载最新版本的 Python 3.10.4\n下载好然后运行 python-3.10.4-amd64.exe, 选择第一个选项，一定要记得勾选添加到 PATH，否则在命令行中执行 python 时会报错。\n准备 Python 一键下载脚本 用 Chrome 浏览器打开 https://github.com/yihong0618/Kindle_download_helper，下载 ZIP 包后并且解压:\n使用 Python 一键下载脚本 进入解压后的目录，点击鼠标右键打开一个命令行窗口\n打开的命令行窗口是这样的：\n接下来准备 python 的虚拟环境，在命令行窗口输入：\npython -m venv venv 创建好虚拟环境之后，再激活虚拟环境，在命令行窗口输入：\n.\\venv\\Scripts\\activate.bat 安装 Python 脚本的依赖库，在命令行窗口输入：\npip install -r requirements.txt 至此，一键下载脚本准备就绪，在命令行窗口输入：\npython kindle.py 这样看到了脚本的帮助提示：\n接下来我们准备脚本需要的 cookie 和 csrf_token, 这两个值都需要通过 Chrome 浏览器获取。\n获取 cookie 和 csrf_token 首先使用浏览器登录亚马逊账号，\n登录地址 https://z.cn， 登录之后访问 https://www.amazon.cn/hz/mycd/myx#/home/content/booksAll/dateDsc/ 点击鼠标右键打开审查元素，\n在网络标签下，察看任意的链接，就可以找到 cookie：\n在页面上点击右键查看页面源代码\n按 Ctrl + F 搜索 csrf_token，\ncookie 和 csrf_token 找到之后，执行命令即可进行下载\npython kindle.py \u0026lt;cookie\u0026gt; \u0026lt;csrf_token\u0026gt; --is-cn 这样就可以自动下载已经购买的电子书了。\ncookie 和 csrf_token 如果过期了，重复上面的步骤获取即可。\n如何阅读下载回来的电子书 存到自己的 Kindle 中阅读 使用自己的 Kindle 序列号清除 DRM 后使用 Calibre 阅读 ","permalink":"https://lewang.dev/posts/2022-06-06-save-your-kindle-ebooks/","summary":"前几天亚马逊宣告 Kindle 中国电子书店运营进行调整，通知标题写得比较委婉，其实就是亚马逊电子书即将退出中国市场，到时候我们购买的国行版 Kindle 将不能购买新电子书，也不能下载已经购买的电子书了。\n如果购买的电子书比较多，手动一本一本的去下载是挺费时间的，我来分享一下我是如何使用 Python 一键下载所有电子书的。\n安装 Python 用 Chrome 浏览器打开 https://python.org，下载最新版本的 Python 3.10.4\n下载好然后运行 python-3.10.4-amd64.exe, 选择第一个选项，一定要记得勾选添加到 PATH，否则在命令行中执行 python 时会报错。\n准备 Python 一键下载脚本 用 Chrome 浏览器打开 https://github.com/yihong0618/Kindle_download_helper，下载 ZIP 包后并且解压:\n使用 Python 一键下载脚本 进入解压后的目录，点击鼠标右键打开一个命令行窗口\n打开的命令行窗口是这样的：\n接下来准备 python 的虚拟环境，在命令行窗口输入：\npython -m venv venv 创建好虚拟环境之后，再激活虚拟环境，在命令行窗口输入：\n.\\venv\\Scripts\\activate.bat 安装 Python 脚本的依赖库，在命令行窗口输入：\npip install -r requirements.txt 至此，一键下载脚本准备就绪，在命令行窗口输入：\npython kindle.py 这样看到了脚本的帮助提示：\n接下来我们准备脚本需要的 cookie 和 csrf_token, 这两个值都需要通过 Chrome 浏览器获取。\n获取 cookie 和 csrf_token 首先使用浏览器登录亚马逊账号，\n登录地址 https://z.cn， 登录之后访问 https://www.","title":"Kindle 中国停止运营，一键下载已购买的电子书"},{"content":"前几天刷新闻的时候，看到了一个代码仓库 YouTubeDrive 的介绍，他的作者在多年前用 Wolfram 语言（也就是 Mathematica） 编写了一段代码，可以将油管变成一个无限容量的大网盘，看完介绍之后我脑子里直接蹦出无数个“卧槽”，心里顿生“这样也行”的膜拜之情，不禁提问：这个是薅羊毛的最高境界吗？\n说起 Wolfram 语言和 Mathematica，不得不提一下，它是我学习的第一个编程语言（汇编是第二个）。多年前我是在学校机房学的，当时只用它画画数学公式的图，今天看着 YouTubeDrive 的实现，才了解到 Wolfram 还可以这样用。\n首先，和原作者一样，我也不提倡这样把 YouTube 当成一个无限容量的网盘使用，相同的道理，我也不提倡在国内把 B 站或者任意视频平台当做位无限容量的网盘来用。\n昨天在看完 YouTubeDrive 源码之后，想着要不我也用 Python 实现一个，正好傍晚溜娃的时候，啥也不能干，就边走边想，我用 Python 应该怎么来实现一个？ 在坐着等娃的时候，就用手机查下资料，大概了解了一下 numpy 和 opencv 的使用，晚上吃过晚饭就开始写了，很快也实现了一个可以工作的 Python 版的。\n我觉得这个想法还是挺有意思的，虽然不太靠谱，但是真的好玩。不想去读 Wolfram 源码的朋友可以看看我接下来的分析，看看是如何把文件变成 YouTube 视频的进行存储的。\n磁盘上的文件是什么 在我们的计算机（无论电脑还是手机都是计算机）磁盘里，存放了很多很多的文件，例如图片，文档，视频等等，每种文件都有自己的类型，或者叫格式，一般我们都会给文件添加一个后缀名展示文件的类型，但是对于电脑而言，无论是什么文件，它都只是由比特（bit） 0 和 1 组成的数据。\n例如这张图片：\n用 vim 执行命令 :%!xxd，打开是这样的：\n图片文件的头 4 个字节是 ffd8（其实 ffd8 说明了这个是 JPEG 格式的文件），ffd8 是 16 进制的表示法，比 2 进制看起来更清晰。\n在计算机里，用 8 个 bit 表示一个 byte，即 1 个字节有 8 位，一个字节需要用 2 个 16 进制数来表示，例如：ffd8 是 2 个字节。\n视频文件又是什么 视频文件也是有各种类型的，对计算机而言，视频文件也是由比特 0 和 1 组成的数据，但是视频文件的头部是有规律的，这样计算机就能区分去视频文件的类型。\n视频通过播放器播放，人类去看视频的时候，眼睛看到的其实是一帧一帧播放的图片，超过每秒 20 帧，也就是一秒中播放 20 张图片，人类的大脑就会认为这是一个连续的画面，这样的画面就叫做视频，存放这些图片数据的就是视频文件。\n我们姑且不讨论视频中声音的部分，在后续我把文件转成视频的过程中，没有产生声音。其实声音也可以用来存储数据的，比如我们可以把文件编码成摩斯密码声波存储到视频中去。\n图片是什么 既然视频是由图片组成的，那图片又是什么？我们在日常的工作或者生活中，会常常需要调整图片长宽大小，我们可以简单的用像素来表述图片的长宽，例如上面手提包图片的长宽是 1024x768, 表示长有 1024 个像素点，宽有 768 个像素点。\n每一个像素点都有一种颜色，这个颜色我们可以用 RGB 来表示。例如这样的颜色值#ff0000，表示红色，#00ff00 表示绿色，#0000ff 表示蓝色，用 3 个字节就可以表示颜色值了，颜色值还有另外的表示方法，例如红色可以用这样来表示：(ff, 00, 00) 或者 (255, 0, 0)，用了方便人类阅读，我们使用 (255, 0, 0) 这种方式来表示像素点的颜色。\n任意文件如何图片来表示 文件是一个连续的由 0 和 1 组成的二进制数据，我们可以用一个一维的十进制数组来表示这个文件：假设文件由 n 个 bit 组成，那么这个一维数组的长度就是 n，数组的每一个元素对应着文件中的每一个 bit。我们再给十进制数组的每一个元素乘以 255 后，会得到一个新的数组，如图：\n这样十进制数组每 3 个元素就可以表示一个像素点的颜色了\n我们只要准备一个足够大的图片，就可以把这个数组中的数据拷贝到这个图片中去，这样就可以把文件转成图片了。\n用视频来存放图片 我们不需要准备一张足够大的图片，我们可以用很多张小的图片来分块存储这些像素点，但是考虑到 YouTube 视频在上传后会被平台转成不同的画幅大小，所以我们可以把图片分成不同的尺寸，比如我们可以把图片分成 1280x720。\n在代码实现过程中，一张原始图片的长宽像素是 64 x 36，这样每张图片可以存储 2304 个比特位的数据，考虑到视频在平台被转码压缩后会有颜色变化，我们用 20x20 的小色块来表示一个像素点，那么 1280x720 的视频一帧恰好也是存储 2304 个比特位。\n视频我选择的 fps 是 20，也就是每秒播放 20 张图片，计算一些简单的计算，很容易就可以得到一个完整的视频。\n将视频转回文件 由于视频在转码的过程中会有一些数据丢失，在转回的过程中，需要对 20x20 的色块进行采样，计算出平均值后就很轻易的反推出比特位是 0 还是 1 了。\n代码实现 我用 Python 实现的 youtube-drive: https://github.com/lewangdev/youtube-drive 原版 Wolfram 实现的 YouTubeDrive: https://github.com/dzhang314/YouTubeDrive ","permalink":"https://lewang.dev/posts/2022-05-27-store-files-as-youtube-videos/","summary":"前几天刷新闻的时候，看到了一个代码仓库 YouTubeDrive 的介绍，他的作者在多年前用 Wolfram 语言（也就是 Mathematica） 编写了一段代码，可以将油管变成一个无限容量的大网盘，看完介绍之后我脑子里直接蹦出无数个“卧槽”，心里顿生“这样也行”的膜拜之情，不禁提问：这个是薅羊毛的最高境界吗？\n说起 Wolfram 语言和 Mathematica，不得不提一下，它是我学习的第一个编程语言（汇编是第二个）。多年前我是在学校机房学的，当时只用它画画数学公式的图，今天看着 YouTubeDrive 的实现，才了解到 Wolfram 还可以这样用。\n首先，和原作者一样，我也不提倡这样把 YouTube 当成一个无限容量的网盘使用，相同的道理，我也不提倡在国内把 B 站或者任意视频平台当做位无限容量的网盘来用。\n昨天在看完 YouTubeDrive 源码之后，想着要不我也用 Python 实现一个，正好傍晚溜娃的时候，啥也不能干，就边走边想，我用 Python 应该怎么来实现一个？ 在坐着等娃的时候，就用手机查下资料，大概了解了一下 numpy 和 opencv 的使用，晚上吃过晚饭就开始写了，很快也实现了一个可以工作的 Python 版的。\n我觉得这个想法还是挺有意思的，虽然不太靠谱，但是真的好玩。不想去读 Wolfram 源码的朋友可以看看我接下来的分析，看看是如何把文件变成 YouTube 视频的进行存储的。\n磁盘上的文件是什么 在我们的计算机（无论电脑还是手机都是计算机）磁盘里，存放了很多很多的文件，例如图片，文档，视频等等，每种文件都有自己的类型，或者叫格式，一般我们都会给文件添加一个后缀名展示文件的类型，但是对于电脑而言，无论是什么文件，它都只是由比特（bit） 0 和 1 组成的数据。\n例如这张图片：\n用 vim 执行命令 :%!xxd，打开是这样的：\n图片文件的头 4 个字节是 ffd8（其实 ffd8 说明了这个是 JPEG 格式的文件），ffd8 是 16 进制的表示法，比 2 进制看起来更清晰。\n在计算机里，用 8 个 bit 表示一个 byte，即 1 个字节有 8 位，一个字节需要用 2 个 16 进制数来表示，例如：ffd8 是 2 个字节。","title":"写个程序把油管 YouTube 变成无限容量的网盘"},{"content":"趁着周末空隙把自己年久失修博客整理了一下，用到了 grep，xargs 和 sed 命令，使用这 3 个命令组合批量替换了所有 Markdown 文件中的图片链接。\n我的博客从我的学生时代就开始了，不断折腾自己的博客，所以学会了 HTML/CSS/JS，下次和大家分享一下我的博客旅程。\n这里不详细讲解这 3 个命令的使用，这 3 个命令的使用可以写出来厚厚的一本书。如果希望详细的了解这些命令，可以在 Linux 的终端下输入这样的命令查看文档，例如查看 grep 如何使用：\nman grep man 命令的常用操作:\n退出: 按 q 键 查找：按 / 后输入查找关键字 下一页: 按 ctrl + d 或者 ctrl + f，tips: 这里的 d 大概是 down，f 大概是 forward 上一页: 按 ctrl + u 或者 ctrl + b，tips: 这里的 d 大概是 up，b 大概是 backward 查看其它命令的手册也是类似的。\n我博客的问题 我的博客是用 hugo 构建的，所有的文章都放在 post 下面的目录里，图片则需要放在 static 的 images 下面，这样在用 vscode 写博客 Markdown 的时候无法在预览里面看到图片。于是我在 hugo 项目的根目录下添加了的一个 images 的符号链接到 static 目录下的 images， 才能解决图片预览的问题。\n另外博客里面的图片越来越多也导致博客的 Git 仓库的体积越来越大，会导致上传下载都变得较慢，于是决定把博客里面的图片二进制文件单独放到一个 GitHub 仓库里面去，这个存放图片等二进制文件的仓库地址是 picb0，让博客仓库本身只保留一些主题文件和文章的 Markdown。\n开始替换 grep 使用 grep 命令查找出所有包括图片的文件，并且显示将要被替换的文件内容\ngrep -nr \u0026#39;/images\u0026#39; . 其中：\n-n: 表示查找文件的名字并且显示匹配的文件行号和内容 -r: 表示递归查找当前目录下的所有目录 输入结果是这样的：\nsed 我常用的 sed 是这样替换文件内容，一般处理单个文件内容的替换，我会直接用 vim 来进行，替换的语法和 sed 也是差不多的。\nsed -i \u0026#39;s/oldstr/newstr/g\u0026#39; filename 其中：\n-i: 表示修改文件后保持到原文件名，并且创建一个 .bak 结尾的备份 oldstr：为需要被替换的字符串 newstr：为替换后的字符串 为了修改博客 Markdown 文件的图片链接，需要这样来修改\nsed -i \u0026#39;s/\\/images/https:\\/\\/raw.githubusercontent.com\\/lewangdev\\/picb0\\/main\\/oh-my-blog/g\u0026#39; 2013-03-04-so-load-path-in-linux.markdown 注意如果 oldstr 或者 newstr 中有 / 符号，需要进行转义为 \\/, 所以这里:\noldstr \\/images newstr https:\\/\\/raw.githubusercontent.com\\/lewangdev\\/picb0\\/main\\/oh-my-blog xargs 弄清楚 grep 和 sed 之后，在使用 xargs 和|(管道符)把这几个命令连起来\ngrep -rl \u0026#34;oldstr\u0026#34; .| xargs sed -i \u0026#39;s/oldstr/newstr/g\u0026#39; grep 的 n 参数换成了 l，因为我只需要文件名 xargs 命令可以使用分割标准输入的内容作为另外一个命令的参数 替换为我博客的实际需要替换的内容：\ngrep -rl \u0026#34;/images\u0026#34; .| xargs sed -i \u0026#39;s/\\/images/https:\\/\\/raw.githubusercontent.com\\/lewangdev\\/picb0\\/main\\/oh-my-blog/g\u0026#39; 使用 git log -p 查看一下是否替换成功\n其它的替换方法 除了命令行，我们使用 ide 或者编辑器也很容易进行替换，比如 vscode 和 idea 系列的。\nvscode idea ","permalink":"https://lewang.dev/posts/2022-05-21-replace-strings-in-all-files/","summary":"趁着周末空隙把自己年久失修博客整理了一下，用到了 grep，xargs 和 sed 命令，使用这 3 个命令组合批量替换了所有 Markdown 文件中的图片链接。\n我的博客从我的学生时代就开始了，不断折腾自己的博客，所以学会了 HTML/CSS/JS，下次和大家分享一下我的博客旅程。\n这里不详细讲解这 3 个命令的使用，这 3 个命令的使用可以写出来厚厚的一本书。如果希望详细的了解这些命令，可以在 Linux 的终端下输入这样的命令查看文档，例如查看 grep 如何使用：\nman grep man 命令的常用操作:\n退出: 按 q 键 查找：按 / 后输入查找关键字 下一页: 按 ctrl + d 或者 ctrl + f，tips: 这里的 d 大概是 down，f 大概是 forward 上一页: 按 ctrl + u 或者 ctrl + b，tips: 这里的 d 大概是 up，b 大概是 backward 查看其它命令的手册也是类似的。\n我博客的问题 我的博客是用 hugo 构建的，所有的文章都放在 post 下面的目录里，图片则需要放在 static 的 images 下面，这样在用 vscode 写博客 Markdown 的时候无法在预览里面看到图片。于是我在 hugo 项目的根目录下添加了的一个 images 的符号链接到 static 目录下的 images， 才能解决图片预览的问题。","title":"grep，xargs 和 sed 的一次日常使用"},{"content":"今天要改 Docker 的两个配置 hostconfig.json 和 config.v2.json，打开一看，这个文件是单行的，在编辑器里面显示这是这样的：\n代码都堆在一起，用 vim 格式化也没啥效果，编辑起来不是很方便。\nPython 自带模块格式化 JSON 格式化 JSON 文件有很多方法，我最常用的就是 Python 自带的 json.tool 这个模块，只要安装了 Python 的电脑，都可以直接使用，这样就可以了：\npython -m json.tool hostconfig.json \u0026gt; hostconfig-formatted.json 格式化以后再打开看一看：\n这样编辑文件就方便多了。\n与管道符一起使用 前面的例子我们还可以使用管道符(|)来实现，例如:\ncat hostconfig.json | python -m json.tool \u0026gt; hostconfig-formatted.json 从 RESTful 接口直接获取数据并且格式化:\ncurl -s https://api.github.com/users/lewangdev | python -m json.tool 其它本地格式化工具 DevToysMac: MacOS 版的 DevToys or Windows，我是用 Windows 11 时候看到这个软件的，于是顺手搜了一下 MacOS 版的，也挺好用的。 jq: 除了像 sed 一样的处理 JSON 以外，还能把输出的 JSON 变成彩色的。 curl \u0026#39;https://api.github.com/users/lewangdev\u0026#39; | jq \u0026#39;.\u0026#39; 输出这样效果：\n","permalink":"https://lewang.dev/posts/2022-05-20-format-json-in-command-line-copy/","summary":"今天要改 Docker 的两个配置 hostconfig.json 和 config.v2.json，打开一看，这个文件是单行的，在编辑器里面显示这是这样的：\n代码都堆在一起，用 vim 格式化也没啥效果，编辑起来不是很方便。\nPython 自带模块格式化 JSON 格式化 JSON 文件有很多方法，我最常用的就是 Python 自带的 json.tool 这个模块，只要安装了 Python 的电脑，都可以直接使用，这样就可以了：\npython -m json.tool hostconfig.json \u0026gt; hostconfig-formatted.json 格式化以后再打开看一看：\n这样编辑文件就方便多了。\n与管道符一起使用 前面的例子我们还可以使用管道符(|)来实现，例如:\ncat hostconfig.json | python -m json.tool \u0026gt; hostconfig-formatted.json 从 RESTful 接口直接获取数据并且格式化:\ncurl -s https://api.github.com/users/lewangdev | python -m json.tool 其它本地格式化工具 DevToysMac: MacOS 版的 DevToys or Windows，我是用 Windows 11 时候看到这个软件的，于是顺手搜了一下 MacOS 版的，也挺好用的。 jq: 除了像 sed 一样的处理 JSON 以外，还能把输出的 JSON 变成彩色的。 curl \u0026#39;https://api.","title":"命令行格式化 JSON"},{"content":"本项目正在调整，请稍等再使用\n一个对国内开发者可能有点用的网络工具，cndevnet 帮助开发者，尤其是初学者不用再费心折腾 pip/npm/golang/flutter/android/blockchain 等等无法访问的问题。\n背景 由于某些原因，国内开发者需要的不少技术站点或者镜像源都无法直接访问，这使得在开发者在日常工作中会消耗额外的时间，去设置各种代理或者寻找国内的可替代的镜像源来使用。\n在开源软件的世界里，几乎所有的知名项目，原本只要把代码 clone 回来，不需要做额外任何配置，只需要按照它的说明就可以直接编译源码。但由于网络的原因，在国内甚至连开源软件的源码都不太方便直接下载，一些原本只需要从 GitHub 拉下来代码就可以直接编译成功的项目，在国内也是连编译都编译不过。\n国内开发者群体是非常庞大的，所以我看到了很多知名的开源项目为中国用户在 README.md 文档或是页面的显著位置都增加了中文提示，去引导国内开发者去使用国内的镜像。\nFlutter\npowerlevel10k\n但是，我想这种做法应该不属于 i18n，因为我很少看到有引导日本的开发者去用日本的镜像，或者引导德国的开发者去用德国的镜像，这看似解决了访问慢或者无法访问的问题，但其实是加剧了国内开发者与全球互联网的割裂，在开发者的世界里面，也形成了国内和国外两个网络。\n给系统或软件单独设置代理，或者寻找国内的可替代的镜像源我都觉得不是一个好方案，因为这样在国内的你总会遇到各种各样的国外开发者就不会遇到的问题，最好还是能够拥有一个与国外开发者“用起来”好像是一样的网络环境。\ncndevnet 就是为了解决这个问题而创建的，希望能帮助到和我一样遇到相同问题的国内开发者。\n从 Linux 开始 cndevnet 可以在 Linux 环境下搭建一个专为初学开发者设置的开发网络，搭配使用 vscode 进行远程开发是一种不错的体验，适合学习 python/golang 等。\nLinux 环境可以是在国内云平台上的一台云主机，也可以是运行在本地虚拟机里面的主机，或者是局域网中的一台 Linux 主机。\nDebian 是一个非常棒的 Linux 发行版，推荐希望学习 Linux 的初学者使用。如果希望使用带有图形界面的 Linux 系统，Ubuntu 也是一个非常好的选择。\ncndevnet 使用了以下软件和服务 Debian：目前支持的 Linux 系统 Docker: 用于运行 gost 和 smartdns 服务 gost: 用于建立 WSS(Websocket over TLS) 服务 smartdns： 用于解决 DNS 污染 dnsmasq-china-list： 国内域名 Cloudflare IP Range: Cloudflare 的 IP 范围 Cloudflare: Cloudflare 的 WSS 代理服务 Oracle Cloud: Oracle Cloud 的云主机 cndevnet 原理 cndevnet 通过 iptables 等工具和数据实现了系统的透明代理，由于是系统级的透明代理，所以在启用了 cndevnet 的 Linux 系统上，是不需要为系统或者各类开发工具或者服务单独设置代理的。\ncndevnet 是免费的 cndevnet 所使用的所有的软件都是开源免费的，另外 cndevnet 使用了 Cloudflare 的免费版域名和 CDN 服务以及 Oracle Cloud 的永久免费的云主机。\n正是因为有 Cloudflare 和 Oracle Cloud 的免费服务，我才想到可以用它们来搭建一个给国内初学者使用的开发环境。\n但是不得不说，这是一种薅羊毛的行为。资源有限，为了防止滥用，cndevnet 的密钥每天会在北京时间 0 点重新生成，如果希望继续使用，请在使用前，从 GitHub 拉取最新的代码来获取正确的密钥。\n安装和使用 请访问 cndevnet 查看。\n","permalink":"https://lewang.dev/posts/2022-05-16-cndevnet-intro/","summary":"本项目正在调整，请稍等再使用\n一个对国内开发者可能有点用的网络工具，cndevnet 帮助开发者，尤其是初学者不用再费心折腾 pip/npm/golang/flutter/android/blockchain 等等无法访问的问题。\n背景 由于某些原因，国内开发者需要的不少技术站点或者镜像源都无法直接访问，这使得在开发者在日常工作中会消耗额外的时间，去设置各种代理或者寻找国内的可替代的镜像源来使用。\n在开源软件的世界里，几乎所有的知名项目，原本只要把代码 clone 回来，不需要做额外任何配置，只需要按照它的说明就可以直接编译源码。但由于网络的原因，在国内甚至连开源软件的源码都不太方便直接下载，一些原本只需要从 GitHub 拉下来代码就可以直接编译成功的项目，在国内也是连编译都编译不过。\n国内开发者群体是非常庞大的，所以我看到了很多知名的开源项目为中国用户在 README.md 文档或是页面的显著位置都增加了中文提示，去引导国内开发者去使用国内的镜像。\nFlutter\npowerlevel10k\n但是，我想这种做法应该不属于 i18n，因为我很少看到有引导日本的开发者去用日本的镜像，或者引导德国的开发者去用德国的镜像，这看似解决了访问慢或者无法访问的问题，但其实是加剧了国内开发者与全球互联网的割裂，在开发者的世界里面，也形成了国内和国外两个网络。\n给系统或软件单独设置代理，或者寻找国内的可替代的镜像源我都觉得不是一个好方案，因为这样在国内的你总会遇到各种各样的国外开发者就不会遇到的问题，最好还是能够拥有一个与国外开发者“用起来”好像是一样的网络环境。\ncndevnet 就是为了解决这个问题而创建的，希望能帮助到和我一样遇到相同问题的国内开发者。\n从 Linux 开始 cndevnet 可以在 Linux 环境下搭建一个专为初学开发者设置的开发网络，搭配使用 vscode 进行远程开发是一种不错的体验，适合学习 python/golang 等。\nLinux 环境可以是在国内云平台上的一台云主机，也可以是运行在本地虚拟机里面的主机，或者是局域网中的一台 Linux 主机。\nDebian 是一个非常棒的 Linux 发行版，推荐希望学习 Linux 的初学者使用。如果希望使用带有图形界面的 Linux 系统，Ubuntu 也是一个非常好的选择。\ncndevnet 使用了以下软件和服务 Debian：目前支持的 Linux 系统 Docker: 用于运行 gost 和 smartdns 服务 gost: 用于建立 WSS(Websocket over TLS) 服务 smartdns： 用于解决 DNS 污染 dnsmasq-china-list： 国内域名 Cloudflare IP Range: Cloudflare 的 IP 范围 Cloudflare: Cloudflare 的 WSS 代理服务 Oracle Cloud: Oracle Cloud 的云主机 cndevnet 原理 cndevnet 通过 iptables 等工具和数据实现了系统的透明代理，由于是系统级的透明代理，所以在启用了 cndevnet 的 Linux 系统上，是不需要为系统或者各类开发工具或者服务单独设置代理的。","title":"如何打造国内开发所需要的网络"},{"content":"gost 是一个非常有用的网络工具，可以在 ShadowsocksX-NG 中安装 gost 插件，方便在 ShadowsocksX-NG 中使用 gost。\n原由 自从查资料上网工具换成 gost 之后，由于 MacOS 上没有 gost 专用的智能代理（也就是该翻的时候翻，不用翻的时候不翻）桌面客户端，所以需要用 gost 在本地把 wss 代理转成 ss 后再继续使用 ShadowsocksX-NG。 虽然可以用 launchctl 启动一个 gost 后台服务，但是用起来还是不太方便。\n看了下 ShadowsocksX-NG 是如何工作的： ShadowsocksX-NG 会在本地启动 ss-local 进程跑一个 socks5 服务，而且 ShadowsocksX-NG 实现智能代理的逻辑与 ss-local 并没有太多的关系，所以只要在想办法本地能提供一个 socks5 服务就够了。\n于是写了以下脚本替换了 ShadowsocksX-NG 安装目录下的 ss-local(替换之前要备份一下这个文件)\n#!/bin/sh - #/opt/gost/gost -L=sock5://:1086 -F=wss://username:password@1.1.1.1:443 # Use gost.json #{ # \u0026#34;Retries\u0026#34;: 3, # \u0026#34;Debug\u0026#34;: false, # \u0026#34;ServeNodes\u0026#34;: [ # \u0026#34;socks5://127.0.0.1:1086\u0026#34; # ], # \u0026#34;ChainNodes\u0026#34;: [ # \u0026#34;wss://username:password@1.1.1.1:443\u0026#34; # ] #} /opt/gost/gost -C /opt/gost/gost.json 替换之后，ShadowsocksX-NG 可以正常工作，但是这样 ShadowsocksX-NG 的其它设置就无法工作了，所以用 python3 把功能加强了一下，编写了这个脚本。\n安装插件前的准备 安装好 ShadowsocksX-NG 并至少启动过一次 系统已经安装好 python3 手动安装插件 手动安装过程包括以下几个步骤，主要是文件的替换：\n下载 gost 并解压到目录 \u0026quot;${HOME}/Library/Application Support/ShadowsocksX-NG/gost\u0026quot;， 确保 \u0026quot;${HOME}/Library/Application Support/ShadowsocksX-NG/gost\u0026quot; 目录下可执行文件名称为 gost 备份 /Applications/ShadowsocksX-NG.app/Contents/Resources/ss-local 为 /Applications/ShadowsocksX-NG.app/Contents/Resources/real-ss-local 用 https://raw.githubusercontent.com/lewangdev/gost-ss-local/master/ss-local 替换 /Applications/ShadowsocksX-NG.app/Contents/Resources/ss-local 退出 ShadowsocksX-NG 应用，再打开即可正常使用 通过自动安装脚本安装插件 把上面的手动安装的过程变成自动安装的脚本\ncurl -L https://github.com/lewangdev/ShadowsocksX-NG-GostPlugin/raw/master/gost-plugin-installer | bash 设置 ShadowsocksX-NG 客户端的配置并不能与 gost 的配置对应上，gost-ss-local 使用了 Address，Port，Password，Plugin，Plugin Opts 来设置 gost。假设 gost 在服务器 1.2.3.4 上是这样启动的：\ngost -L wss://username:password@:443 那么在 Plugin 使用 gost 之后，各参数设置如下:\nPlugin，如果希望使用 gost，那么 Plugin 需要填写 gost，例如 gost，不填或填其它内容，则与 ShadowsocksX-NG 原行为一致 Address, 表示 gost 的服务器地址，可以是 IP 或域名, 例如填写 1.2.3.4 Port, 表示 gost 的端口, 例如填写 443 Password, 由于 ShadowsocksX-NG 不能设置用户名，密码这里需要填写 gost 的用户名和密码，格式为 username:password, 例如填写 username:password Plugin Opts, 如果填写了插件参数，则前 1-3 的设置无效，并且会把 Plugin Opts 填写的内容直接全部传给 gost 命令。 1-3 的设置要求 gost 服务器端为 wss 代理，如果为其它类型的代理，可以通过设置 Plugin Opts 的参数来设置，例如与前面 1-3 等价的 Plugin Opts 配置为 -L socks5://127.0.0.1:1086 -F wss://username:password@1.2.3.4:443\n说明 目前脚本只测试了 gost，对于 SIP003 Plugin 是否影响没有做过测试，如果有问题，欢迎前往代码仓库提 Issues。\n","permalink":"https://lewang.dev/posts/2020-12-02-add-gost-plugin-for-shadowsocks-ng/","summary":"gost 是一个非常有用的网络工具，可以在 ShadowsocksX-NG 中安装 gost 插件，方便在 ShadowsocksX-NG 中使用 gost。\n原由 自从查资料上网工具换成 gost 之后，由于 MacOS 上没有 gost 专用的智能代理（也就是该翻的时候翻，不用翻的时候不翻）桌面客户端，所以需要用 gost 在本地把 wss 代理转成 ss 后再继续使用 ShadowsocksX-NG。 虽然可以用 launchctl 启动一个 gost 后台服务，但是用起来还是不太方便。\n看了下 ShadowsocksX-NG 是如何工作的： ShadowsocksX-NG 会在本地启动 ss-local 进程跑一个 socks5 服务，而且 ShadowsocksX-NG 实现智能代理的逻辑与 ss-local 并没有太多的关系，所以只要在想办法本地能提供一个 socks5 服务就够了。\n于是写了以下脚本替换了 ShadowsocksX-NG 安装目录下的 ss-local(替换之前要备份一下这个文件)\n#!/bin/sh - #/opt/gost/gost -L=sock5://:1086 -F=wss://username:password@1.1.1.1:443 # Use gost.json #{ # \u0026#34;Retries\u0026#34;: 3, # \u0026#34;Debug\u0026#34;: false, # \u0026#34;ServeNodes\u0026#34;: [ # \u0026#34;socks5://127.0.0.1:1086\u0026#34; # ], # \u0026#34;ChainNodes\u0026#34;: [ # \u0026#34;wss://username:password@1.","title":"写了一个 ShadowsocksX-NG 的 gost 插件"},{"content":"创业这段时间以来，我们的 IoT 系统已经在不少客户的机房做了私有化部署，客户大多都是机加工厂、商业大楼、医院和大学实验室等，客户的机房都有一个相同的特点：私有云，与外网隔离，不能访问互联网。或者更为准确的说，是我们部署的服务器不能访问互联网，在没有互联网访问权限的情况下，系统的包管理工具(yum/apt/docker)都无法使用了，在这种情况下进行系统部署安装，费时费力，而且无法进行远程部署维护，也大大增加了项目的实施成本。\n在最近的一个客户项目的实施过程中，看到客户的一些其他供应商在系统部署过程中非常艰难，甚至是 CentOS 系统初始化和 Docker 的安装就花掉了两个礼拜的人力，不排除一些供应商这样折腾会给他的客户留下工作敬业钱花的值的好印象，但对于我们这样的小创业公司来说，这样的时间浪费和低效是无法承担的成本，因为来实操部署的人就是公司老板本人了，我也不想出差在客户这里待上两个礼拜。\n在工作完成之后，想想可以把解决问题的方法记录一下，也许能给遇到相同问题的同行一些启发和帮助。好了，废话不多说，接下来我们就来解决这个无外网的部署问题，顺便再解决一下远程维护的问题。\n一、面临的问题 在部署和维护一个私有化企业内部使用且无互联网访问的系统中，可能会面临以下问题:\n机房服务器无法访问 Internet 可通过接入企业内网来访问服务器，而从服务器无法直连办公室网络，即机房和办公室在不同的网段 无互联网访问权限的情况下，无法直接使用系统的配置工具，如 yum/apt/docker 等，配置系统和部署服务费时费力 无法远程进行维护 客户内网拓扑示意图，此处省略了防火墙，简化拓扑图的复杂度，如下:\n网络拓扑图说明:\n机房和办公室不在一个网段 机房网段假设为 172.22.0.0/16，办公室内网 WiFi 的网段为 192.168.137.0/24 机房服务器之间是互通的，办公室可以 ping 通机房服务器，在机房服务器上无法 ping 通办公室部署控制主机，这里假设办公室网络作为一个 NAT 放在上层交换机后面 内网没有互联网访问权限，包括机房服务器和办公室内网 WiFI 二、解决问题的方法 既然是由于机房服务器没有互联网访问权限，不能联网下载安装包，那就想办法让机房服务器可以连接互联网或者搭建内网的软件包镜像服务，于是想到一些方法来达到目的:\n告知客户问题，申请开通互联网访问权限，可以限定为指定的网址和协议(HTTP/HTTPS)，需要远程维护的化，还需要申请可以连接到服务器的 VPN。这个方法在需要客户的进行审批，可能时间比较长。我们的客户中有不少都没有自建 VPN，或这不方便给我们开通 VPN，另外也无法开通需要我们部署的服务器的互联网访问权限，所以不能使用这个方法\n在客户机房放置可以通过 4G 上网的堡垒机，堡垒机接入客户机房网络。在客户机房放一个堡垒机，虽然说是堡垒机，但可能客户那边的机房管理也还是不容许放置这样的机器的，所以也不能使用这个方法\n搭建 yum(CentOS)/apt(Debian/Ubuntu)/docker 的内网镜像服务，搭建内网镜像服务可能就是一个比较艰巨的任务，难以接受给自己又添加了一个艰难的任务，此方法也作罢\n通过在办公室网里放置部署控制机，同时连接内网和 4G 路由器提供的外网(互联网)，在部署控制机上搭建 SDWAN 或者 HTTP 代理，yum/apt/docker 都支持 HTTP 代理，这样修改系统配置之后，就可以通过代理安装部署服务，这个方法比较简单，而且几乎不需要客户的参与就可以完成。不过也有一些前置条件，例如:\n办公室中主机可以接入客户网络\n客户服务器允许办公室网络中主机通过 TCP 或者 UDP 访问其任意或者指定的端口\n客户办公室的 4G 网络信号要足够好，这样可以提供更好的外网速度\n拥有服务器的 root 管理员权限(需要安装软件和修改系统配置)\n通过权衡利弊，我们最终使用的是第 4 个方法来搭建的，下面来讲讲搭建的过程。\n三、搭建可以访问互联网的部署环境 我们希望使用第 4 个方法来搭建部署的网络环境，通过测试客户的内网环境，几个前置条件都已满足，这样第 4 个方法的搭建过程不需要客户的参与就可以完成。首先要解决的是网络通路问题，我们在客户办公室添加了一台 4G 路由器，添加 4G 路由器之后的网络拓扑图如图:\n部署控制主机连接好 4G 路由器之后，获取到一个 192.168.8.106 的 IP，注意 4G 路由器的网段要与办公室网段不同，例如办公室网段是 192.168.137.0/24 (也就是子网掩码为 255.255.255.0)，4G 路由器的网段为 192.168.8.0/24，也就是说部署控制主机需要有两个网络接口(网卡)：一个接内网，一个接外网（4G 路由器），如果部署控制主机刚好有一个 RJ45 的以太网卡和一个 WiFI 网卡（很多笔记本电脑就是这样），那部署控制主机在硬件上就不需要在添加额外的硬件了。\n关于网络搭建过程中的使用到硬件就讲这些，各位读者可以根据自己的情况选用合适的网络设备，比如 4G 无线网卡，带 4G 模块的工控主机等等，原则上只要能建立一个互联网访问的通道就行了。\n接下来我们再一起探讨一下如果利用开源软件来搭建互联网访问的通道，如图：\n现在解决问题的思路应该更加清晰了：需要在机房服务器和办公室部署控制主机之间建立一个通道。建立通道可以使用的开源软件有不少，相信很多人因为某些原因都或多或少使用过，从我自己长期实际使用的经验来看，frp 和 WireGuard 非常适合用在当前情况下建立通道，gost 也十分容易搭建 HTTP 或者 SOCK5 代理。\n可以用一句话来描述搭建的过程：\n在机房一台服务器上面安装 frp 或 WireGuard 做服务端，在可以连接外网内网的部署控制主机上安装 frp 或 WireGuard 做客户端，并且在部署控制主机上安装 gost 服务启用 HTTP 代理服务，最后在服务器端的修改系统配置，让 yum 和 docker 拉镜像走 HTTP 代理。\n因为部署系统使用 HTTP 代理就够了，这样 frp + gost 的组合可能比 WireGuard + gost 的组合配置起来会更加简单，WireGuard 是一个可以用于 SDWAN 组网的高性能通道软件，本文就不说明如何使用它来搭建，如果对 WireGuard 搭建 SDWAN 感兴趣，可以自行研究一下或者留言私信给我。\n以下使用 frp + gost 来说明具体的搭建过程。\n3.1 准备需要的软件 请根据自己部署控制主机对应的平台下载需要的软件\nfrp 是一个专注于内网穿透的高性能的反向代理应用，支持 TCP、UDP、HTTP、HTTPS 等多种协议。可以将内网服务以安全、便捷的方式通过具有公网 IP 节点的中转暴露到公网。 Windows x86_64 系统请下载 frp_0.34.3_windows_amd64.zip Linux x86_64 系统请下载 frp_0.34.3_linux_amd64.tar.gz MacOS 系统请下载 frp_0.34.3_darwin_amd64.tar.gz gost 是 GO 语言实现的安全隧道 Windows x86_64 系统请下载 gost-windows-amd64-2.11.1.zip Linux x86_64 系统请下载 gost-linux-amd64-2.11.1.gz MacOS 系统请下载 gost-darwin-amd64-2.11.1.gz 如果部署控制主机是 ARM 或者 MIPS 架构的，需要上面两个软件需要下载对应的版本来安装，在使用上没有任何差异。\n3.2 配置路由和安装软件 frp 和 gost 安装都比较简单，上传到服务器上解压后就就可以了，我们只需要在其中一台机房服务器上安装 frp 作为服务端启动，机房其它服务器不需要安装 frp，总结一下需要进行的安装步骤：\n在部署控制主机上面配置路由，使得部署控制主机可以访问机房网络也可以访问互联网 其中一台机房服务器上安装 frp，作为服务端启动 部署控制主机上安装 gost，启动一个 HTTP 代理服务器 部署控制主机上安装 frp，作为客户端启动 以下部署过程以部署控制主机系统为 MacOS，机房服务器系统为 CentOS 7.6 为例，如果读者在实际部署过程中由于系统与本文不同而遇到问题不知如何解决，比如服务器为 Ubuntu，部署机是 Windows 10，欢迎留言一起探讨。\n在部署控制主机上面配置路由 如果部署控制主机和机房服务器在同一个子网，则不需要添加路由。由于本次部署控制主机和机房服务器不在一个子网，在接了两个网卡之后，在部署控制主机上的 IP 包是不知道如何路由到机房网络的，如果不做路由设置，172.22.0.0/16 网段会走系统默认的路由，可能无法访问到机房网络。所以在部署控制主机上（ MacOS 系统）添加以下路由:\nsudo route add -net 172.22.0.0/16 192.168.137.1 如果部署控制主机为 Linux 或者 Windows 也需要手动添加路由。\n在机房任意一台服务器上安装 frp 上传 frp_0.34.3_linux_amd64.tar.gz 到服务器，解压 frp_0.34.3_linux_amd64.tar.gz 安装包\n$ tar -zxvf frp_0.34.3_linux_amd64.tar.gz frp_0.34.3_linux_amd64/ frp_0.34.3_linux_amd64/frps_full.ini frp_0.34.3_linux_amd64/frps.ini frp_0.34.3_linux_amd64/frpc frp_0.34.3_linux_amd64/frpc_full.ini frp_0.34.3_linux_amd64/frps frp_0.34.3_linux_amd64/LICENSE frp_0.34.3_linux_amd64/frpc.ini 进入 frp_0.34.3_linux_amd64 目录并新建 frps-deployment.ini 文件，内容为\n[common] bind_port = 7000 找到 frps 命令后启动：\n$ ./frps -c frps-deployment.ini 2020/11/30 06:53:01 [I] [service.go:128] frps tcp listen on 0.0.0.0:7000 2020/11/30 06:53:01 [I] [root.go:190] Start frps success 如果服务无法启动，可能是 7000 端口已经被占用或者是 frps 没有执行权限，请注意具体的报错信息。\n在部署控制主机上安装 gost 解压 gost 包，并且添加执行权限\n$ gunzip gost-darwin-amd64-2.11.1.gz $ chmod +x gost-darwin-amd64-2.11.1 启动 HTTP 代理, 在端口 18080 上监听\n$ ./gost-darwin-amd64-2.11.1 -L http://:18080 在控制主机上安装 frp 解压 frp 安装包后在目录中新建 frpc-deployment.ini 文件，内容为\n[common] server_addr = 192.168.52.79 server_port = 7000 [gost] type = tcp local_ip = 127.0.0.1 local_port = 18080 remote_port = 18080 作为客户端启动 frpc\n$ ./frpc -c frpc-deployment.ini 2020/11/30 14:59:30 [I] [proxy_manager.go:300] proxy removed: [] 2020/11/30 14:59:30 [I] [proxy_manager.go:310] proxy added: [gost] 2020/11/30 14:59:30 [I] [proxy_manager.go:333] visitor removed: [] 2020/11/30 14:59:30 [I] [proxy_manager.go:342] visitor added: [] 2020/11/30 14:59:30 [I] [control.go:246] [f51d0bf5d26ef627] login to server success, get run id [f51d0bf5d26ef627], server udp port [0] 2020/11/30 14:59:30 [I] [control.go:169] [f51d0bf5d26ef627] [gost] start proxy success 至此通道就搭建完成了，如图所示。\n3.3 启用代理 机房服务器使用的都是 CentOS 7.6 的系统，需要用到的 yum 和 docker 也都支持通过 HTTP 代理进行软件包和容器镜像的下载，如果使用的是 ubuntu 系统，通过 apt 也可以配置为通过 HTTP 代理下载软件包，以下还是以 CentOS 7.6 系统为例。\nyum 启用 http 代理 修改 /etc/yum.conf 文件，添加代理配置\nproxy=http://127.0.0.1:18080 我们测试一下代理是否成功\n$ sudo yum makecache Loaded plugins: fastestmirror Loading mirror speeds from cached hostfile epel/x86_64/metalink | 23 kB 00:00:01 * base: mirrors.neusoft.edu.cn * epel: ftp.iij.ad.jp * extras: ftp.sjtu.edu.cn * updates: ftp.sjtu.edu.cn base | 3.6 kB 00:00:00 docker-ce-stable | 3.5 kB 00:00:00 epel | 4.7 kB 00:00:00 extras | 2.9 kB 00:00:00 updates | 2.9 kB 00:00:00 (2/3): epel/x86_64/primary_db 20% [=================== ] 790 kB/s | 4.6 MB 00:00:22 ETA 代理看起来没有问题，我们来安装 git 试试。\n$ sudo yum install -y git Loaded plugins: fastestmirror Loading mirror speeds from cached hostfile * base: mirrors.neusoft.edu.cn * epel: d2lzkl7pfhq30w.cloudfront.net * extras: ftp.sjtu.edu.cn * updates: ftp.sjtu.edu.cn Resolving Dependencies --\u0026gt; Running transaction check ---\u0026gt; Package git.x86_64 0:1.8.3.1-23.el7_8 will be installed --\u0026gt; Processing Dependency: perl-Git = 1.8.3.1-23.el7_8 for package: git-1.8.3.1-23.el7_8.x86_64 --\u0026gt; Processing Dependency: perl(Git) for package: git-1.8.3.1-23.el7_8.x86_64 --\u0026gt; Running transaction check ---\u0026gt; Package perl-Git.noarch 0:1.8.3.1-23.el7_8 will be installed --\u0026gt; Finished Dependency Resolution Dependencies Resolved ============================================================================================================================================================================================================================================ Package Arch Version Repository Size ============================================================================================================================================================================================================================================ Installing: git x86_64 1.8.3.1-23.el7_8 base 4.4 M Installing for dependencies: perl-Git noarch 1.8.3.1-23.el7_8 base 56 k Transaction Summary ============================================================================================================================================================================================================================================ Install 1 Package (+1 Dependent package) Total download size: 4.5 M Installed size: 22 M Downloading packages: (1/2): perl-Git-1.8.3.1-23.el7_8.noarch.rpm | 56 kB 00:00:01 (2/2): git-1.8.3.1-23.el7_8.x86_64.rpm | 4.4 MB 00:00:06 -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Total 669 kB/s | 4.5 MB 00:00:06 Running transaction check Running transaction test Transaction test succeeded Running transaction Installing : perl-Git-1.8.3.1-23.el7_8.noarch 1/2 Installing : git-1.8.3.1-23.el7_8.x86_64 2/2 Verifying : git-1.8.3.1-23.el7_8.x86_64 1/2 Verifying : perl-Git-1.8.3.1-23.el7_8.noarch 2/2 Installed: git.x86_64 0:1.8.3.1-23.el7_8 Dependency Installed: perl-Git.noarch 0:1.8.3.1-23.el7_8 Complete! 太令人激动了，我们的代理起作用了，接下来可以愉快的安装软件了。\ndocker 启用 http 代理 首先需要通过 yum 安装 docker 服务，关于 docker 的安装本文不再说明，请参考官方文档 Install Docker Engine on CentOS。安装完 docker 服务之后，通过以下步骤来配置 Docker 服务使用 HTTP 代理\n创建目录和文件\nsudo mkdir -p /etc/systemd/system/docker.service.d sudo touch /etc/systemd/system/docker.service.d/http-proxy.conf 添加以下内容到配置文件 http-proxy.conf 中\n[Service] Environment=\u0026#34;HTTP_PROXY=http://127.0.0.1:18080\u0026#34; Environment=\u0026#34;NO_PROXY=localhost,127.0.0.1\u0026#34; 重启 docker 服务，使得配置生效\nsudo systemctl daemon-reload sudo systemctl restart docker 验证配置\nsudo systemctl show --property=Environment docker Environment=HTTP_PROXY=http://127.0.0.1:18080 NO_PROXY=localhost,127.0.0.1 我们来运行一个 docker 镜像试试\n$ sudo docker run hello-world Unable to find image \u0026#39;hello-world:latest\u0026#39; locally latest: Pulling from library/hello-world 0e03bdcc26d7: Pull complete Digest: sha256:e7c70bb24b462baa86c102610182e3efcb12a04854e8c582838d92970a09f323 Status: Downloaded newer image for hello-world:latest Hello from Docker! This message shows that your installation appears to be working correctly. To generate this message, Docker took the following steps: 1. The Docker client contacted the Docker daemon. 2. The Docker daemon pulled the \u0026#34;hello-world\u0026#34; image from the Docker Hub. (amd64) 3. The Docker daemon created a new container from that image which runs the executable that produces the output you are currently reading. 4. The Docker daemon streamed that output to the Docker client, which sent it to your terminal. To try something more ambitious, you can run an Ubuntu container with: $ docker run -it ubuntu bash Share images, automate workflows, and more with a free Docker ID: https://hub.docker.com/ For more examples and ideas, visit: https://docs.docker.com/get-started/ 通过代理拉取 docker 镜像也成功了，这样就可以直接从我们的公网上搭建的 docker registry 服务拉取待部署的镜像，真是太省事了。\n命令行全局启用代理 也可以在 shell 中启用 HTTP 代理，请根据需要启用\n$ export HTTP_PROXY=”http://127.0.0.1:18080” 在机房其它服务器上使用 HTTP 代理 我们是把 frp 安装到 172.22.121.110 这台服务器上并启动了 frps 服务，通过 frp 的穿透，相当于在 172.22.121.110 这台服务器上启动了一个 HTTP 代理，在 172.22.121.110 本机上使用 http://127.0.0.1:18080 来使用 HTTP 代理，在机房其它服务器上则需要使用 http://172.22.121.110:18080 来使用 HTTP 代理。\n四、实验测试环境的搭建 需要 CentOS 7 环境，且虚拟机不能访问外网\n使用 vagrant 启动一个 centos/7 虚拟机，并且使用桥接模式获取局域网 IP, 例如 IP 为 192.168.52.79 在网关路由器上进行配置使得测试的 CentOS 7 虚拟机无法访问互联网，如果网关路由器是个 linux based 的系统则可以尝试用 iptables 限制虚拟机访问互联网: iptables -A FORWARD -s 192.168.52.79 -j DROP 五、远程维护 通过前面关于 frp 的使用，不难看出来，只要在部署控制主机再启动一个 frp 客户端，把机房或者部署控制主机的 SSH 端口映射到到公网中的 frp 服务器可以了。当然这种情况下还在需要多做一些配置工作，比如在部署控制主机上配置好服务的自启动，然后把部署控制主机放在客户的办公室，在需要远程维护的时候，开机就好。\n对于远程维护，使用 frp 可能不太安全，毕竟直接把 SSH 端口暴露在公网之中，所以在这种情况下，推荐使用 WireGuard 进行 SDWAN 组网，把部署控制主机加入到自建的私有网络中。\n","permalink":"https://lewang.dev/posts/2020-11-27-go-through-the-system-firewall/","summary":"创业这段时间以来，我们的 IoT 系统已经在不少客户的机房做了私有化部署，客户大多都是机加工厂、商业大楼、医院和大学实验室等，客户的机房都有一个相同的特点：私有云，与外网隔离，不能访问互联网。或者更为准确的说，是我们部署的服务器不能访问互联网，在没有互联网访问权限的情况下，系统的包管理工具(yum/apt/docker)都无法使用了，在这种情况下进行系统部署安装，费时费力，而且无法进行远程部署维护，也大大增加了项目的实施成本。\n在最近的一个客户项目的实施过程中，看到客户的一些其他供应商在系统部署过程中非常艰难，甚至是 CentOS 系统初始化和 Docker 的安装就花掉了两个礼拜的人力，不排除一些供应商这样折腾会给他的客户留下工作敬业钱花的值的好印象，但对于我们这样的小创业公司来说，这样的时间浪费和低效是无法承担的成本，因为来实操部署的人就是公司老板本人了，我也不想出差在客户这里待上两个礼拜。\n在工作完成之后，想想可以把解决问题的方法记录一下，也许能给遇到相同问题的同行一些启发和帮助。好了，废话不多说，接下来我们就来解决这个无外网的部署问题，顺便再解决一下远程维护的问题。\n一、面临的问题 在部署和维护一个私有化企业内部使用且无互联网访问的系统中，可能会面临以下问题:\n机房服务器无法访问 Internet 可通过接入企业内网来访问服务器，而从服务器无法直连办公室网络，即机房和办公室在不同的网段 无互联网访问权限的情况下，无法直接使用系统的配置工具，如 yum/apt/docker 等，配置系统和部署服务费时费力 无法远程进行维护 客户内网拓扑示意图，此处省略了防火墙，简化拓扑图的复杂度，如下:\n网络拓扑图说明:\n机房和办公室不在一个网段 机房网段假设为 172.22.0.0/16，办公室内网 WiFi 的网段为 192.168.137.0/24 机房服务器之间是互通的，办公室可以 ping 通机房服务器，在机房服务器上无法 ping 通办公室部署控制主机，这里假设办公室网络作为一个 NAT 放在上层交换机后面 内网没有互联网访问权限，包括机房服务器和办公室内网 WiFI 二、解决问题的方法 既然是由于机房服务器没有互联网访问权限，不能联网下载安装包，那就想办法让机房服务器可以连接互联网或者搭建内网的软件包镜像服务，于是想到一些方法来达到目的:\n告知客户问题，申请开通互联网访问权限，可以限定为指定的网址和协议(HTTP/HTTPS)，需要远程维护的化，还需要申请可以连接到服务器的 VPN。这个方法在需要客户的进行审批，可能时间比较长。我们的客户中有不少都没有自建 VPN，或这不方便给我们开通 VPN，另外也无法开通需要我们部署的服务器的互联网访问权限，所以不能使用这个方法\n在客户机房放置可以通过 4G 上网的堡垒机，堡垒机接入客户机房网络。在客户机房放一个堡垒机，虽然说是堡垒机，但可能客户那边的机房管理也还是不容许放置这样的机器的，所以也不能使用这个方法\n搭建 yum(CentOS)/apt(Debian/Ubuntu)/docker 的内网镜像服务，搭建内网镜像服务可能就是一个比较艰巨的任务，难以接受给自己又添加了一个艰难的任务，此方法也作罢\n通过在办公室网里放置部署控制机，同时连接内网和 4G 路由器提供的外网(互联网)，在部署控制机上搭建 SDWAN 或者 HTTP 代理，yum/apt/docker 都支持 HTTP 代理，这样修改系统配置之后，就可以通过代理安装部署服务，这个方法比较简单，而且几乎不需要客户的参与就可以完成。不过也有一些前置条件，例如:\n办公室中主机可以接入客户网络\n客户服务器允许办公室网络中主机通过 TCP 或者 UDP 访问其任意或者指定的端口\n客户办公室的 4G 网络信号要足够好，这样可以提供更好的外网速度\n拥有服务器的 root 管理员权限(需要安装软件和修改系统配置)\n通过权衡利弊，我们最终使用的是第 4 个方法来搭建的，下面来讲讲搭建的过程。","title":"机房不能访问互联网，轻松搞定系统部署"},{"content":" 小码哥：“与大象（Gradle）一见如故？你就是 Gradle？”\n大象：“对，我就是那个用来构建 Java 项目的 Gradle 大象。 ”\n小码哥：“我好像天天都在用你啊。看，我的项目都是用你构建的。”\n大象：“但你不一定真的认识我，你每次修改点构建代码时是不是都要问下谷哥哥（Google）或溢栈哥哥（StackOverflow）？”\n小码哥：“额。。。”\n大象：“我是你的老朋友了，不要天天如初见哦，咱们得多聊聊，我爸妈给我写了传记（文档），估计你也懒得细看，不如我给你做个自我介绍吧。”\n小码哥：“👌ok”\n大象：“你有用 InteliJ IDEA 吗？ ”\n小码哥：“对，用的社区版。”\n大象：“那我就用 InteliJ IDEA CE 版来给你介绍自己”。\n你好，我是 Gradle！ Gradle 是一个用来自动化构建项目的的工具，可以用来构建你常用的 Java、Kotlin 等 JVM 语言开发的项目。我的配置文件可以使用 Gvoovy 或者 Kotlin 来编写，不像隔壁前辈 Maven 那样，要写一大段 XML，你可以很开心的像写代码一样来调整我的配置。\n其实我就是你的代码自动化产线，产线有几个工序（Task），你负责喂我代码，我负责打包（Jar/War/JavaDoc/Test）。是不是觉得与科幻片里面的工厂很像？\n创建项目 请打开你的 InteliJ IDEA CE，使用 idea 可以很方便的创建一个由我构建的项目(File \u0026gt; New \u0026gt; Project\u0026hellip;)。\n这里在对话框的左边栏选择 Gradle，右边选择 Java 项目。\n输入好项目名之后点击 Finish 就可以啦。\n探索项目 idea 帮我们创建了一个 Java 项目，浏览项目很容易发现两个以 gradle 为结尾的文件： build.gradle 和 settings.gradle。我们来看一下它们俩的内容。\nsettings.gradle\nrootProject.name = \u0026#39;MyProject\u0026#39; build.gradle\nplugins { id \u0026#39;java\u0026#39; } group \u0026#39;org.example\u0026#39; version \u0026#39;1.0-SNAPSHOT\u0026#39; sourceCompatibility = 1.8 repositories { mavenCentral() } dependencies { testCompile group: \u0026#39;junit\u0026#39;, name: \u0026#39;junit\u0026#39;, version: \u0026#39;4.12\u0026#39; } 小码哥插话：“settings.gradle 很好懂，给 rootProject.name 这个变量赋了一个值，但是 build.gradle 里面写的是什么啊？”\n大象：“gradle 的魔法确实就在 build.gradle 之中。build.gradle 是使用 Groovy 编写的脚本，我先介绍一下 Groovy 同学吧。”\n你好，Groovy 同学 Gradle 的构建脚本可以使用 Groovy 编写（Kotlin 也可以）。Groovy 也是一种 JVM 语言，并且与 Java 比较相似，写起来比 Java 要简洁得多 (Kotlin 也是)，Groovy 是 Java 的超集，也就是说 Java 代码可以当成是 Groovy 代码运行。我们来了解一下 Groovy，以便更容易读懂和修改 build.gradle。\n初识 Groovy 在 idea 中打开 Groovy 控制台（Tools \u0026gt; Groovy Console\u0026hellip;）\n接着在控制台里输入一些 Java 代码\n点击绿色三角形来运行这段代码，可以在下面的窗口看到输出\n小码哥：“大象，你说这是 Java 代码，为什么没有定义 class 和 main 函数呢？”\n大象：“只是看起来是 Java，其实这个就是 Groovy 了，Grovvy 脚本不需要定义 class 和 main 函数就可以执行的，我们继续把它变得更 Groovy 一些吧。”\n由于 Groovy 会自动导入 System.out，我们可以把代码简写成这样\n是不是看起来与 Java 里面函数静态导入写起来一样，我们可以更进一步：\n在 Groovy 里面如果函数只有一个参数，那么括弧可以省略; 语句末尾的分号也可以省略； 单引号和双引号是（大部分情况下）可以替换（推荐用双引号，方便迁移到 Kotlin DSL） 现在很清楚了：原来 println 是一个函数，在代码里面调用了它，并且给它参数传了一个字符串 my project。\nGroovy 闭包(Groovy Closure) 提到闭包，应该或多或少都听说过，Gradle 的 build.gradle 脚本中大量使用了闭包，我们还是用一段代码来说明。\n先定义一个类\n这个类看起来和 Java 没什么区别，我们再添加一个可以接受 Closure 类型参数的函数\n大象：“是不是看起来与 Java 8 的 Lambda 非常的像，比如 Function 接口？”\n小码哥：“是的，难道是一样的东西，只是不同语言写法不同？那在 Groovy 里面如何来使用闭包呢？”\n大象：“正是如此，我们来看下如何在 Groovy 里面来使用闭包吧。”\n首先需要创建一个 MyProject 实例，然后再调用 doTask 方法。\n同样点击绿色三角形，运行这段代码\n我们再回过头来看看 build.gradle, 比如这段代码\ndependencies { testCompile group: \u0026#39;junit\u0026#39;, name: \u0026#39;junit\u0026#39;, version: \u0026#39;4.12\u0026#39; } 是不是很好理解：脚本调用了 dependencies 方法，它的参数是一个闭包，在这个闭包里面又调用了 testCompile 方法，并传入了一个字典（Map）参数： group: \u0026lsquo;junit\u0026rsquo;, name: \u0026lsquo;junit\u0026rsquo;, version: \u0026lsquo;4.12\u0026rsquo;\n好了，Groovy 同学暂时就介绍到这里，我要继续介绍我自己了。\n探索 build.gradle Gradle 最核心的部分就是 Project 和 Task 了 ，build.gradle 可以直接操控 Project 对象（org.gradle.api.Project 的实例) ，遵循“代码即配置”的基本原则。项目代码可以包括多个 build.gradle, 多个 Project 需要在 settings.gradle 定义。每一个 build.gradle 文件都会对应一个 Project 对象，我们每一个项目的构建信息都会存在对应的 Project 对象中。\n站在写代码的角度上可以这样来理解，Gradle 会根据 settings.gradle 定义为每个 Project 创建 org.gradle.api.Project 对象，之后会根据 build.gradle 的配置执行相应的任务（Task）。除了“代码即配置”这个原则之外，还有“约定即配置”这个原则。比如 Gradle 会有默认约定的 Java 项目结构（Java Plugin Project Layout），每一个 Gradle 项目也会约定一些默认的 Task 等。\n也就是说，Gradle 的 Project 构建过程是由一系列 Task 组成的，我们可以通过这个命令查看项目默认的有哪些 Task。好，我们把 idea 创建的 build.gradle 清空，然后执行下面的命令。\ngradle tasks 可以看到所有默认的 Task。\n接着一步步恢复 idea 默认创建的 build.gradle，先往空的 build.gradle 文件中添加：\nplugins { id \u0026#39;java\u0026#39; } 可以理解这段代码了么？没错，我们往 build.gradle 中添加了 Java 插件，这样就可以很方便的构建 Java 项目，再次查看 Task。\n插件可以在 Project 中添加 Task，Gradle 会把这些 Task 构建成一个有向无环图（DAG，Directed Acyclic Graph），加了 Java 插件之后的 DAG 是这样的。\n每一个 Project 包括一系列的 Task，同样，每个 Project 也有很多的属性，例如添加这里代码来修改属性：\ngroup \u0026#39;org.example\u0026#39; version \u0026#39;1.0-SNAPSHOT\u0026#39; sourceCompatibility = 1.8 我们可以在 build.gradle 中对 Project 的属性进行修改，也可以像查看 Task 一样来查看 Project 所有的属性：\ngradle properties 哇，Project 的属性还真不少（只截取了部分属性）。\n自定义 Task Gradle 的构建过程包括三个阶段：初始化（Initialization）、配置（Configuration）和执行（Execution），为了弄清楚这三个阶段，我们在 build.gradle 中添加这样代码：\nprintln \u0026#34;config\u0026#34; task task1 { println \u0026#34;config in task1\u0026#34; } task task2 { doLast { println \u0026#34;do last in task2\u0026#34; } println \u0026#34;config in task2\u0026#34; } task task3 { doFirst { println \u0026#34;do first in task3\u0026#34; } doLast { println \u0026#34;do last in task3\u0026#34; } println \u0026#34;config in task3\u0026#34; } 在 settings.gradle 中添加：\nprintln \u0026#34;init\u0026#34; 然后执行：\ngradle task2 task3 可以看到这样的输出：\n从输出日志中可以清晰的看到，Gradle 第一步初始化，然后进行配置，最后执行具体任务，如图所示：\n弄清楚构建过程之后，我们可以很容易根据我们的需要对 Task 进行自定义配置或者是新建一个 Task。\n老朋友，你好 大象：“我就说到这里，有没有一见如故的感觉？我的 API 非常多，想弄清楚我的细枝末节还是得去看文档，希望我的这个介绍能帮助到你去理解文档。 ”\n小码哥：“有种恍然大悟的感觉，配置 Gradle 确实就像写 Java 代码差不多，原来 Gradle 只用通过 build.gradle 想办法修改 Project 对象就可以了。哦对，我还个问题，gradlew 是什么啊？”\n大象：“呃，gradlew 我也不知道它是啥啊，你自己查查去吧。”\n参考资料 PyScript 官网 Pyodide - 基于 WebAssembly 的 Python ","permalink":"https://lewang.dev/posts/2020-02-22-hello-gradle-1/","summary":"小码哥：“与大象（Gradle）一见如故？你就是 Gradle？”\n大象：“对，我就是那个用来构建 Java 项目的 Gradle 大象。 ”\n小码哥：“我好像天天都在用你啊。看，我的项目都是用你构建的。”\n大象：“但你不一定真的认识我，你每次修改点构建代码时是不是都要问下谷哥哥（Google）或溢栈哥哥（StackOverflow）？”\n小码哥：“额。。。”\n大象：“我是你的老朋友了，不要天天如初见哦，咱们得多聊聊，我爸妈给我写了传记（文档），估计你也懒得细看，不如我给你做个自我介绍吧。”\n小码哥：“👌ok”\n大象：“你有用 InteliJ IDEA 吗？ ”\n小码哥：“对，用的社区版。”\n大象：“那我就用 InteliJ IDEA CE 版来给你介绍自己”。\n你好，我是 Gradle！ Gradle 是一个用来自动化构建项目的的工具，可以用来构建你常用的 Java、Kotlin 等 JVM 语言开发的项目。我的配置文件可以使用 Gvoovy 或者 Kotlin 来编写，不像隔壁前辈 Maven 那样，要写一大段 XML，你可以很开心的像写代码一样来调整我的配置。\n其实我就是你的代码自动化产线，产线有几个工序（Task），你负责喂我代码，我负责打包（Jar/War/JavaDoc/Test）。是不是觉得与科幻片里面的工厂很像？\n创建项目 请打开你的 InteliJ IDEA CE，使用 idea 可以很方便的创建一个由我构建的项目(File \u0026gt; New \u0026gt; Project\u0026hellip;)。\n这里在对话框的左边栏选择 Gradle，右边选择 Java 项目。\n输入好项目名之后点击 Finish 就可以啦。\n探索项目 idea 帮我们创建了一个 Java 项目，浏览项目很容易发现两个以 gradle 为结尾的文件： build.gradle 和 settings.gradle。我们来看一下它们俩的内容。\nsettings.gradle","title":"大象（Gradle）的故事：一见如故"},{"content":"昨日陪小糍粑参加幼儿园秋游活动，游览了一下位于松江区的方塔园，校方安排幼儿在白色布袋上绘画，于是有了以下大作。\n观察幼儿绘画的过程挺有意思的，我们父母无须提供任何绘画的指导，只要等待小朋友们绘画完毕，来听听他们关于自己的画的解释即可。\n关于上面的画，小糍粑是这样解释的。\n他和爸爸来看方塔，想象和爸爸在方塔里面，草地的旁边有水，有果树(梨树)，有蓝天，蓝天上面有飞机在飞，他穿着红色的衣服和黑色的鞋子。\n我感觉他画的非常自信，有考虑绘画的布局，所有的线条几乎都是一笔成型，丝毫不拖沓，很符合这个年龄幼儿的表现。\n图里面有个地方是我画的，大家可以猜猜是哪里。\n","permalink":"https://lewang.dev/posts/2019-11-02-fangta-paintings/","summary":"昨日陪小糍粑参加幼儿园秋游活动，游览了一下位于松江区的方塔园，校方安排幼儿在白色布袋上绘画，于是有了以下大作。\n观察幼儿绘画的过程挺有意思的，我们父母无须提供任何绘画的指导，只要等待小朋友们绘画完毕，来听听他们关于自己的画的解释即可。\n关于上面的画，小糍粑是这样解释的。\n他和爸爸来看方塔，想象和爸爸在方塔里面，草地的旁边有水，有果树(梨树)，有蓝天，蓝天上面有飞机在飞，他穿着红色的衣服和黑色的鞋子。\n我感觉他画的非常自信，有考虑绘画的布局，所有的线条几乎都是一笔成型，丝毫不拖沓，很符合这个年龄幼儿的表现。\n图里面有个地方是我画的，大家可以猜猜是哪里。","title":"游方塔园"},{"content":"背景 搭建 VPN 方便连接无公网 IP 云主机进行开发，WireGuard 配置比 OpenVPN 要简单很多，WireGuard 是通过 Linux 内核模块的方式实现的，这样性能最好，但是只能用在 Linux 系统上。本文使用的 wireguard-go， 则是使用 Golang 实现的 WireGuard 协议，属于用户空间(User Space)的实现，性能没有内核模块方式好，但好处就是跨平台且更简单易用。\nVPN 的用处非常的多，不像 frp 之类的端口穿透应用，它是直接建立虚拟的网络，网络中的每个客户端也都可以拥有自己独立的 IP，于是测试调试就没有了端口的限制，非常的方便。\n除了方便安全连接云服务器，还可以通过 VPN 搭建工业设备的远程部署和维护的解决方案。\n编译 wireguard-go # 使用 GitHub 的源码镜像，速度会快一些 git clone git@github.com:wireguard/wireguard-go.git # 在 MacOS 下交叉编译 CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -v -o \u0026#34;wireguard-go\u0026#34; # Linux 环境下直接编译 go build -v -o \u0026#34;wireguard-go\u0026#34; cp wireguard-go /usr/sbin/ 编译 WireGuard tools git clone git@github.com:wireguard/wireguard.git # 安装依赖 ## debian\u0026amp;ubuntu sudo apt-get install libmnl-dev libelf-dev ## centos sudo yum install libmnl-devel elfutils-libelf-devel cd wireguard/src/tools make sudo make install 配置 WireGuard 生成密钥\ncd /etc/wireguard wg genkey | tee server_privatekey | wg pubkey \u0026gt; server_publickey 生成服务器端配置文件 wg0.conf\necho \u0026#34;[Interface] PrivateKey = $(cat server_privatekey) Address = 10.0.0.1/24 PostUp = iptables -A FORWARD -i wg0 -o eth0 -j ACCEPT; iptables -A FORWARD -i eth0 -o wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE PostDown = iptables -D FORWARD -i wg0 -o eth0 -j ACCEPT; iptables -D FORWARD -i eth0 -o wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE ListenPort = 1194 MTU = 1420 \u0026#34;\u0026gt; wg0.conf 生成客户端配置文件 client-wg0.conf\necho \u0026#34;[Interface] PrivateKey = $(cat client_privatekey) Address = 10.0.0.2/24 DNS = 8.8.8.8 MTU = 1420 [Peer] PublicKey = $(cat server_publickey) Endpoint = ${server_ip}:1194 AllowedIPs = 0.0.0.0/0, ::0/0 PersistentKeepalive = 30\u0026#34; | sed \u0026#39;/^#/d;/^\\s*$/d\u0026#39; \u0026gt; client-wg0.conf 在服务器端开启数据包转发\necho 1 \u0026gt; /proc/sys/net/ipv4/ip_forward echo \u0026#34;net.ipv4.ip_forward = 1\u0026#34; \u0026gt;\u0026gt; /etc/sysctl.conf sysctl -p 最后在服务器端启动 WireGuard\nwg-quick up wg0 客户端需要根据前面生成的配置文件进行配置\n参考 https://www.wireguard.com https://medium.com/@xtarin https://www.linode.com/docs ","permalink":"https://lewang.dev/posts/2019-10-30-wireguard-go-setup/","summary":"背景 搭建 VPN 方便连接无公网 IP 云主机进行开发，WireGuard 配置比 OpenVPN 要简单很多，WireGuard 是通过 Linux 内核模块的方式实现的，这样性能最好，但是只能用在 Linux 系统上。本文使用的 wireguard-go， 则是使用 Golang 实现的 WireGuard 协议，属于用户空间(User Space)的实现，性能没有内核模块方式好，但好处就是跨平台且更简单易用。\nVPN 的用处非常的多，不像 frp 之类的端口穿透应用，它是直接建立虚拟的网络，网络中的每个客户端也都可以拥有自己独立的 IP，于是测试调试就没有了端口的限制，非常的方便。\n除了方便安全连接云服务器，还可以通过 VPN 搭建工业设备的远程部署和维护的解决方案。\n编译 wireguard-go # 使用 GitHub 的源码镜像，速度会快一些 git clone git@github.com:wireguard/wireguard-go.git # 在 MacOS 下交叉编译 CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -v -o \u0026#34;wireguard-go\u0026#34; # Linux 环境下直接编译 go build -v -o \u0026#34;wireguard-go\u0026#34; cp wireguard-go /usr/sbin/ 编译 WireGuard tools git clone git@github.com:wireguard/wireguard.git # 安装依赖 ## debian\u0026amp;ubuntu sudo apt-get install libmnl-dev libelf-dev ## centos sudo yum install libmnl-devel elfutils-libelf-devel cd wireguard/src/tools make sudo make install 配置 WireGuard 生成密钥","title":"WireGuard 使用简介"},{"content":"2019/10/13 上午大作 画了一辆坦克\n2019/10/10 晚大作 1 2 3 4 小糍粑自己介绍： 爸爸妈妈宝宝奶奶和他的同学，右一是奶奶，右二是爸爸，右三是他自己，右四是妈妈，奶奶没地方画了\n点评： 很明显的出现头足人像\n","permalink":"https://lewang.dev/posts/2019-10-13-sons-paintings/","summary":"2019/10/13 上午大作 画了一辆坦克\n2019/10/10 晚大作 1 2 3 4 小糍粑自己介绍： 爸爸妈妈宝宝奶奶和他的同学，右一是奶奶，右二是爸爸，右三是他自己，右四是妈妈，奶奶没地方画了\n点评： 很明显的出现头足人像","title":"小糍粑的画"},{"content":"背景 树莓派 raspbian 系统日志默认的配置会导致日志过大而占用太多的存储空间，并且频繁写日志也可能减短 EMMC 和 SD 卡的寿命，需要重新配置来满足项目的需求。\n日志位置 /var/log /var/log/syslog /var/log/daemon.log /var/log/mail.info /var/log/mail.warn /var/log/mail.err /var/log/mail.log /var/log/kern.log /var/log/auth.log /var/log/user.log /var/log/lpr.log /var/log/cron.log /var/log/debug /var/log/messages 配置 rsyslog 与 logrotate rsyslog 的配置文件为 /etc/rsyslog.conf, 找到其中 RULES 段\n############### #### RULES #### ############### # # First some standard log files. Log by facility. # auth,authpriv.* /var/log/auth.log *.*;auth,authpriv.none -/var/log/syslog #cron.* /var/log/cron.log #daemon.* -/var/log/daemon.log kern.* -/var/log/kern.log lpr.* -/var/log/lpr.log mail.* -/var/log/mail.log user.* -/var/log/user.log 将其中 daemon.* -/var/log/daemon.log 行注释掉，syslog中已经包含 daemon 的日志。\n修改 rsyslog 的 logrotate 配置 /etc/logrotate.d/rsyslog\n/var/log/syslog /var/log/daemon.log { rotate 3 daily missingok notifempty nodelaycompress compress postrotate invoke-rc.d rsyslog rotate \u0026gt; /dev/null endscript } /var/log/mail.info /var/log/mail.warn /var/log/mail.err /var/log/mail.log /var/log/kern.log /var/log/auth.log /var/log/user.log /var/log/lpr.log /var/log/cron.log /var/log/debug /var/log/messages { rotate 3 weekly missingok notifempty compress nodelaycompress sharedscripts postrotate invoke-rc.d rsyslog rotate \u0026gt; /dev/null endscript } 将 delaycompress 改为 nodelaycompress ，分割文件后立即压缩归档；另外 rotate 都设置为 3\n立即测试一下配置\nsudo /usr/sbin/logrotate /etc/logrotate.conf 写日志到内存 首先清空 /var/log 目录下面的日志(如果日志不多，可以不用清空)。\n在 /etc/fstab 文件中添加以下内容, /var/log 最大可以使用 100MB 内存，也同时给其他常用的写目录/tmp、/var/tmp 挂载到内存中。\ntmpfs /var/log tmpfs defaults,noatime,nosuid,mode=0755,size=100m 0 0 tmpfs /tmp tmpfs defaults,noatime,nosuid,size=10m 0 0 tmpfs /var/tmp tmpfs defaults,noatime,nosuid,size=10m 0 0 重启系统或者手动挂载文件系统使之生效。\n","permalink":"https://lewang.dev/posts/2019-09-19-rpi-log-config/","summary":"背景 树莓派 raspbian 系统日志默认的配置会导致日志过大而占用太多的存储空间，并且频繁写日志也可能减短 EMMC 和 SD 卡的寿命，需要重新配置来满足项目的需求。\n日志位置 /var/log /var/log/syslog /var/log/daemon.log /var/log/mail.info /var/log/mail.warn /var/log/mail.err /var/log/mail.log /var/log/kern.log /var/log/auth.log /var/log/user.log /var/log/lpr.log /var/log/cron.log /var/log/debug /var/log/messages 配置 rsyslog 与 logrotate rsyslog 的配置文件为 /etc/rsyslog.conf, 找到其中 RULES 段\n############### #### RULES #### ############### # # First some standard log files. Log by facility. # auth,authpriv.* /var/log/auth.log *.*;auth,authpriv.none -/var/log/syslog #cron.* /var/log/cron.log #daemon.* -/var/log/daemon.log kern.* -/var/log/kern.log lpr.* -/var/log/lpr.log mail.* -/var/log/mail.log user.* -/var/log/user.log 将其中 daemon.* -/var/log/daemon.log 行注释掉，syslog中已经包含 daemon 的日志。","title":"树莓派系统日志配置"},{"content":"问题 在使用 Docker 或者 NFS 的时候，需要文件系统挂在到不同的系统中拥有正确的读写权限，需要指定文件所属用户和组的 uid 和 gid。在创建用户和组的时候，系统会自动分配对应的值，这导致在不同的系统中很容易造成 uid 和 gid 不一样而造成读写权限混乱。\n例如在系统 A 中，用户 git 的 uid 和组 git 的gid 如下:\n[sysops@cn-bj-aliyun-3 ~]$ id git uid=1001(git) gid=1001(git) groups=1001(git) 而系统 B 中的值如下:\n[sysops@cn-bj-aliyun-3 ~]$ id git uid=998(git) gid=998(git) groups=998(git) 我们在系统 A 以 git 用户创建目录或者文件:\n[git@cn-bj-aliyun-3 data]$ ls -lh total 60K -rw-r--r-- 1 git git 100 Jul 5 23:22 file1 -rw-r--r-- 1 git git 400 Dec 18 2018 file2 -rw-r--r-- 1 git git 349 Dec 18 2018 file3 将这个目录挂在到 B 系统中时，将会出现这种情况:\n[git@cn-bj-aliyun-3 data]$ ls -lh total 60K -rw-r--r-- 1 1001 1001 100 Jul 5 23:22 file1 -rw-r--r-- 1 1001 1001 400 Dec 18 2018 file2 -rw-r--r-- 1 1001 1001 349 Dec 18 2018 file3 这样就会导致在 B 系统中文件权限混乱，因为 B 系统中没有对应 uid 和 gid 的用户和组。接下来我们来修改 uid 和 gid，以下以修改系统 A 中的 git 为例。\n修改命令 修改 UID sudo usermod -u 998 git 修改文件的所属用户 find / -user 1001 -exec chown -h git {} \\; 修改 GID sudo groupmod -g 998 git 修改文件的所属用户 find / -group 1001 -exec chgrp -h git {} \\; 其它说明 另外在修改之前可以查看 /etc/passwd 和 /etc/group 文件，以便选取一个没有被使用的 uid 和 gid\n","permalink":"https://lewang.dev/posts/2019-07-16-change-uid-gid-in-linux/","summary":"问题 在使用 Docker 或者 NFS 的时候，需要文件系统挂在到不同的系统中拥有正确的读写权限，需要指定文件所属用户和组的 uid 和 gid。在创建用户和组的时候，系统会自动分配对应的值，这导致在不同的系统中很容易造成 uid 和 gid 不一样而造成读写权限混乱。\n例如在系统 A 中，用户 git 的 uid 和组 git 的gid 如下:\n[sysops@cn-bj-aliyun-3 ~]$ id git uid=1001(git) gid=1001(git) groups=1001(git) 而系统 B 中的值如下:\n[sysops@cn-bj-aliyun-3 ~]$ id git uid=998(git) gid=998(git) groups=998(git) 我们在系统 A 以 git 用户创建目录或者文件:\n[git@cn-bj-aliyun-3 data]$ ls -lh total 60K -rw-r--r-- 1 git git 100 Jul 5 23:22 file1 -rw-r--r-- 1 git git 400 Dec 18 2018 file2 -rw-r--r-- 1 git git 349 Dec 18 2018 file3 将这个目录挂在到 B 系统中时，将会出现这种情况:","title":"如何修改 Linux 用户的 UID 和组的 GID"},{"content":"概要 本文记录了在 CentOS 7.6 系统上通过 pyenv 安装 Python 3.7.3 的过程。\n环境 CentOS Linux release 7.6.1810 (Core) Kernel 3 3.10.0-957.el7.x86_64 Pyenv 1.2.11 安装 pyenv $ curl https://pyenv.run | bash 然后根据提示把以下内容放到 ~/.bashrc 文件末尾\n# Load pyenv automatically by adding # the following to ~/.bashrc: export PATH=\u0026#34;/root/.pyenv/bin:$PATH\u0026#34; eval \u0026#34;$(pyenv init -)\u0026#34; eval \u0026#34;$(pyenv virtualenv-init -)\u0026#34; 安装 Python 3.7.3 准备\n$ sudo yum -y install xz bzip2 bzip2-devel sqlite-devel gcc openssl-devel readline-devel zlib-devel libffi-devel 安装\npyenv install 3.7.3 验证\n$ pyenv versions * system (set by /root/.pyenv/version) 3.7.3 参考 https://github.com/pyenv/pyenv/issues/1183 https://github.com/pyenv/pyenv-installer https://github.com/pyenv/pyenv ","permalink":"https://lewang.dev/posts/2019-05-29-centos7-pyenv-install-python-3/","summary":"概要 本文记录了在 CentOS 7.6 系统上通过 pyenv 安装 Python 3.7.3 的过程。\n环境 CentOS Linux release 7.6.1810 (Core) Kernel 3 3.10.0-957.el7.x86_64 Pyenv 1.2.11 安装 pyenv $ curl https://pyenv.run | bash 然后根据提示把以下内容放到 ~/.bashrc 文件末尾\n# Load pyenv automatically by adding # the following to ~/.bashrc: export PATH=\u0026#34;/root/.pyenv/bin:$PATH\u0026#34; eval \u0026#34;$(pyenv init -)\u0026#34; eval \u0026#34;$(pyenv virtualenv-init -)\u0026#34; 安装 Python 3.7.3 准备\n$ sudo yum -y install xz bzip2 bzip2-devel sqlite-devel gcc openssl-devel readline-devel zlib-devel libffi-devel 安装","title":"CentOS 7 上使用 pyenv 安装 Python 3.7.3"},{"content":"自己学习 Java 和用 Java 过程中的理解和备忘的知识点。\nJDK 11 Oracle 不在免费提供 LTS 版本了，包括 JDK 8 ZGC, 可伸缩的、低延迟的垃圾收集器，STW 时间不超过 10ms Nashorn 标记为 Deprecate 了 JDK 9 module try-catch 简化 _ 为保留关键字 var 将在 JDK 10 中作为关键字, 作为本地变量类型推断关键字 字符串相关，主要是 Compact String, 用 byte[] 替换了 char[], char 在 Java 中占两个字节 G1 作为默认垃圾回收器 JVM 启动 JVM 相关的命令行参数 G1 垃圾回收器 内存 JVM(Hotspot) 内存结构 制造各种 OutOfMemery 和 StackOverflow 各种 JVM 相关工具(jstat/jmap/jstack/jps 等) 解决 FullGC 的问题 引用：WeakRefrence/PhantomReference/SoftRefrence/FinalRefrence 等 ClassLoader/URLClassLoader Spring Boot 的 ClassLoader 自定义 ClassLoader 多线程和并发 JVM 的线程模型 1:1 协程 创建线程的方法 线程同步 synchronzied/对象锁/类锁/锁方法/锁锁代码块 线程池 锁相关（Lock 接口和实现类） Condition volatile ThreadLocal 用途和实现 并发（CAS/乐观锁/无锁/无等待/无阻塞） 字节码 基本结构 操作字节码的工具 Javassist/ASM AOP 集合 Collection,List,ArrayList,Vector,LinkedList, SkipList, Stack Queue HashMap/HashTable HashMap 扩容死循环问题 LinkedHashMap TreeMap HashSet,TreeSet WeakHashMap 并发相关的集合 ConcurrentHashMap CopyOnWriteArrayList IO IO 模型 NIO Non-blocking IO Java 8 IO 相关的接口和类 Netty Vert.x 时间/日期 SimpleDateFormat 问题 Java 8 Joda 数值和二进制 数值类型在 Java 中的范围 浮点类型有什么问题 BigDecimal 位运算 格式化，有效数字 泛型 泛型使用中的一些点：类型擦除/父类中使用泛型的函数如何返回子类对象 异常 编译异常 运行时异常 非运行时异常 ","permalink":"https://lewang.dev/posts/2019-03-06-keynotes-of-new-java/","summary":"自己学习 Java 和用 Java 过程中的理解和备忘的知识点。\nJDK 11 Oracle 不在免费提供 LTS 版本了，包括 JDK 8 ZGC, 可伸缩的、低延迟的垃圾收集器，STW 时间不超过 10ms Nashorn 标记为 Deprecate 了 JDK 9 module try-catch 简化 _ 为保留关键字 var 将在 JDK 10 中作为关键字, 作为本地变量类型推断关键字 字符串相关，主要是 Compact String, 用 byte[] 替换了 char[], char 在 Java 中占两个字节 G1 作为默认垃圾回收器 JVM 启动 JVM 相关的命令行参数 G1 垃圾回收器 内存 JVM(Hotspot) 内存结构 制造各种 OutOfMemery 和 StackOverflow 各种 JVM 相关工具(jstat/jmap/jstack/jps 等) 解决 FullGC 的问题 引用：WeakRefrence/PhantomReference/SoftRefrence/FinalRefrence 等 ClassLoader/URLClassLoader Spring Boot 的 ClassLoader 自定义 ClassLoader 多线程和并发 JVM 的线程模型 1:1 协程 创建线程的方法 线程同步 synchronzied/对象锁/类锁/锁方法/锁锁代码块 线程池 锁相关（Lock 接口和实现类） Condition volatile ThreadLocal 用途和实现 并发（CAS/乐观锁/无锁/无等待/无阻塞） 字节码 基本结构 操作字节码的工具 Javassist/ASM AOP 集合 Collection,List,ArrayList,Vector,LinkedList, SkipList, Stack Queue HashMap/HashTable HashMap 扩容死循环问题 LinkedHashMap TreeMap HashSet,TreeSet WeakHashMap 并发相关的集合 ConcurrentHashMap CopyOnWriteArrayList IO IO 模型 NIO Non-blocking IO Java 8 IO 相关的接口和类 Netty Vert.","title":"Java 进阶要点"},{"content":"准备 阅读Flashing the Compute Module eMMC 准备一个可以刷系统的底板，我用的是微雪Compute-Module-IO-Board-Plus 步骤(Linux 系统下刷系统，可以使用树莓派来刷机) sudo apt install git libusb-1.0-0-dev git clone --depth=1 https://github.com/raspberrypi/usbboot \u0026amp;\u0026amp; cd usbboot \u0026amp;\u0026amp; make sudo ./rpiboot 连好线，跳线部分见各自底板的手册 拔掉 USB SLAVE 1/2/3/4 拔掉 SELECT 跳线帽 将 BOOT ENABLE USB SLAVE 跳线帽接到 EN 端 sudo fdisk -l 查看磁盘名称，这里是 /dev/sda sudo dd if=raspbian-lite.img of=/dev/sda bs=4MiB 刷入系统后，挂在启动分区，添加 SSH 来开启 SSH 服务，sudo mount /dev/sda1 /media cd /media \u0026amp;\u0026amp; touch SSH 使用慧通科技出品的 HuiBox-700 底板 省略上述的第4步，其它步骤完全一样\n","permalink":"https://lewang.dev/posts/2019-03-01-rpi-cm3-emmc-flashing/","summary":"准备 阅读Flashing the Compute Module eMMC 准备一个可以刷系统的底板，我用的是微雪Compute-Module-IO-Board-Plus 步骤(Linux 系统下刷系统，可以使用树莓派来刷机) sudo apt install git libusb-1.0-0-dev git clone --depth=1 https://github.com/raspberrypi/usbboot \u0026amp;\u0026amp; cd usbboot \u0026amp;\u0026amp; make sudo ./rpiboot 连好线，跳线部分见各自底板的手册 拔掉 USB SLAVE 1/2/3/4 拔掉 SELECT 跳线帽 将 BOOT ENABLE USB SLAVE 跳线帽接到 EN 端 sudo fdisk -l 查看磁盘名称，这里是 /dev/sda sudo dd if=raspbian-lite.img of=/dev/sda bs=4MiB 刷入系统后，挂在启动分区，添加 SSH 来开启 SSH 服务，sudo mount /dev/sda1 /media cd /media \u0026amp;\u0026amp; touch SSH 使用慧通科技出品的 HuiBox-700 底板 省略上述的第4步，其它步骤完全一样","title":"树莓派 CM3/CM3+ 刷写系统"},{"content":"为什么要写这篇文章？ 曾经几年前在 Docker 还没有广泛应用的时候，在公司使用过源码的方式安装和升级过 Gitlab，虽远没有 Docker 方便，因为自己对 Linux 系统的理解，所以整体上感觉还是挺简单的。这几年随着 Docker 的普及，使得安装 Gitlab 更加的容易，不仅方便了我这样的老鸟，也更方便了小白用户们。但是 Gitlab 官方的 Docker 安装文档并没有写得很完善, 除了官方文档之外，检索出来的安装文档也是人云亦云，东拼西凑，结果也就是能运行起来，凑合着能用而已。避免出现下图中“我已经用 Docker 部署好啦”，其实已经翻车的情形。\n我希望每做一件小事的时候也都能抱着“知其然知其所以然”的心态对待，用 Docker 方式安装 Gitlab，说简单来说就是一行命令的事儿，但是这样就够了吗？我看是不够的，所以就有了这篇文档。\n本文需要达成的事项 在 CentOS 7 系统中安装 Docker 使用 Docker 方式安装中文版 Gitlab 和宿主机器共用 22(SSH) 端口 支持 SSH(22)/HTTPS(443) 方式推拉仓库 使用 SMTP 方式配置通知邮箱(腾讯企业邮箱) 改写默认的项目标签(Labels) 在 CentOS 7 系统中安装 Docker 这部分参考 Docker 的官方文档, 罗列一下安装步骤, 细节请看 Docker 的官方文档。如果使用 root 用户安装，sudo 可以去掉。\n1. 删除老版本 Docker $ sudo yum remove docker \\ docker-client \\ docker-client-latest \\ docker-common \\ docker-latest \\ docker-latest-logrotate \\ docker-logrotate \\ docker-selinux \\ docker-engine-selinux \\ docker-engine 2. 安装 Docker CE 的仓库配置 $ sudo yum install -y yum-utils \\ device-mapper-persistent-data \\ lvm2 $ sudo yum-config-manager \\ --add-repo \\ https://download.docker.com/linux/centos/docker-ce.repo 3. 安装仓库中最高版本 Docker CE $ sudo yum install -y docker-ce 4. 启动 Docker $ sudo systemctl start docker 5. 验证 Docker 是否安装成功 $ sudo docker run hello-world 使用 Docker 方式安装中文版 Gitlab 目前我的团队习惯使用中文版的 Gitlab 的，并且使用的版本是 beginor/gitlab-ce:10.3.1-ce.0，所以还是以这个版本来说明安装配置过程。\n在启动 Gitlab 之前，创建几个目录作为 Docker 的卷，这样的配置或者升级 gitlab 的时候可以保留配置和数据。\n$ sudo mkdir -p /data/var/lib/gitlab/etc $ sudo mkdir -p /data/var/lib/gitlab/log $ sudo mkdir -p /data/var/lib/gitlab/data 启动 Gitlab\n$ sudo docker run \\ --detach \\ --sysctl net.core.somaxconn=1024 \\ --publish 8080:80 \\ --publish 8022:22 \\ --name gitlab \\ --restart unless-stopped \\ --volume /data/var/lib/gitlab/etc:/etc/gitlab \\ --volume /data/var/lib/gitlab/log:/var/log/gitlab \\ --volume /data/var/lib/gitlab/data:/var/opt/gitlab \\ beginor/gitlab-ce:10.3.1-ce.0 这个 Gitlab 的 Docker 镜像是基于 Ubuntu 16.04.3 LTS 这个版本来构建的，所以在 docker exec -it gitlab /bin/bash 进入 Docker 容器之后跟使用 Ubuntu 就没有什么差别了。\ngitlab 容器中启动了很多服务，主要包括：\ngitlab redis postgresql nginx sshd 通过查看 Dockerfile 发现，除了可以使用 docker exec -it gitlab /bin/bash 进入容器之外，还可以直接使用 SSH 登录到容器中。其实 Gitlab 这个镜像，并不符合 Dockerfile 最佳实践规范, 因为将会有太多的服务在这个镜像构建的容器之中，不利于服务的扩容和重用。不过通过这种超级包的方式确实大大降低了用户的使用门槛，对于高段位选手，自然也会自己去拆分去解耦，进而去构建自己的镜像。\n讲到这里，gitlab 服务已经在运行了，大多数人也认为自己任务完成了，一切到此为止。但是对于有些许强迫症的人来说，是无法接受 HTTP 得用 8080 端口， SSH 得用 8022 端口，这样 Git 的 URL 就不太美观了。\n偷懒的人有个很简单的方法来解决这个端口的问题， 可以使用这样的命令启动 Docker\n$ sudo docker stop gitlab $ sudo docker rm gitlab $ sudo docker run \\ --detach \\ --sysctl net.core.somaxconn=1024 \\ --publish 443:443 \\ --publish 80:80 \\ --publish 22:22 \\ --name gitlab \\ --restart unless-stopped \\ --volume /data/var/lib/gitlab/etc:/etc/gitlab \\ --volume /data/var/lib/gitlab/log:/var/log/gitlab \\ --volume /data/var/lib/gitlab/data:/var/opt/gitlab \\ beginor/gitlab-ce:10.3.1-ce.0 这样有带来了新问题:\n80/443 将会被 gitlab 独占，宿主机器上 Nginx 等 HTTP/HTTPS 服务将无法使用 80/443 22 将会被 gitlab 独占，那么宿主机器上的 SSHD 服务需要改为其它端口 这两个新问题大概对于有些许强迫症的人来说也是无法接受的。 我们还是回到这样的方式启动 Gitlab\n$ sudo docker stop gitlab $ sudo docker rm gitlab $ sudo docker run \\ --detach \\ --sysctl net.core.somaxconn=1024 \\ --publish 8080:80 \\ --publish 8022:22 \\ --name gitlab \\ --restart unless-stopped \\ --volume /data/var/lib/gitlab/etc:/etc/gitlab \\ --volume /data/var/lib/gitlab/log:/var/log/gitlab \\ --volume /data/var/lib/gitlab/data:/var/opt/gitlab \\ beginor/gitlab-ce:10.3.1-ce.0 然后寻找其它更好的解决方案：\n使用 Nginx 代理 8080 端口，这样很容易实现 HTTP(80)/HTTPS(443) 端口共用 共享宿主机器的 SSH(22) 端口，如果使用 git 这个账号登录，则转发 SSH 到 gitlab 的容器中 下面来讲如何解决这两个问题。\n使用宿主机器上的 Nginx 配置 HTTP 和 HTTPS 使用宿主机器上的 Nginx 使得我们安装 Gitlab 更加灵活。先停用 Gitlab 容器中的 HTTPS 服务, 需要这样改写配置文件, 需要编辑 /data/var/lib/gitlab/etc/gitlab.rb 文件相应的行，具体配置可以参考这里\nexternal_url \u0026#34;https://gitlab.example.com\u0026#34; nginx[\u0026#39;listen_port\u0026#39;] = 80 nginx[\u0026#39;listen_https\u0026#39;] = false 修改完成之后，可以在 Docker 容器中执行 gitlab-ctl reconfigure 来使之生效。这样配置以后，容器中讲只提供 HTTP 服务，不会根据 external_url 解析来自动启动 HTTPS 而导致日志中出现大量的缺少证书的日志。现在只需要配置宿主机器的 Nginx 就可以了。关于如何获取免费的 SSL 证书，这里就不在赘述了，读者可以自行搜索 Let\u0026rsquo;s Encrypt + Nginx 相关文章，如果没有域名，只有 IP，那可以试试 TrustOcean。\nGitlab Docker 和宿主服务器共享 SSH(22) 端口 Gitlab Docker 镜像中默认使用的 SSH 账户是 git，那能不能在宿主机器上也建一个账户 git，只是当 git 帐号进行操作的时候，我们转发命令到 gitlab 容器呢？答案是肯定的，宿主机器上非 git 帐号就不受影响了，还可以正常使用。\n我们在宿主机器上创建 git 账户，并且使他的 uid 和 gid 和容器中的值完全一样，Gitlab 容器中 uid 和 gid 都是 998, 修改宿主机器中的值，将便于未来容器的升级。\nadduser git id git uid=998(git) gid=998(git) groups=998(git) 要完成共享 22 端口，要求 gitlab 容器中的 git 账户的 uid 和 gid 和宿主机器上完全相同，这样读取 SSH Key 时就不会有权限问题。关于如何修改两个系统中 git 账户的 uid 和 gid，可以通过命令 usermod 和 groupmod 手动修改 git 用户的 uid 和 gid参考文档。或者通过编辑 /data/var/lib/gitlab/etc/gitlab.rb 这个配置文件，修改其中的行，来使用其他的用户具体请参考这里:\nuser[\u0026#39;username\u0026#39;] = \u0026#39;other-git\u0026#39; user[\u0026#39;group\u0026#39;] = \u0026#39;other-git\u0026#39; user[\u0026#39;uid\u0026#39;] = 1001 user[\u0026#39;gid\u0026#39;] = 1001 修改完成之后，可以在 Docker 容器中执行 gitlab-ctl reconfigure 来使之生效.\n在宿主机器上切换到 git 账户：su - git 然后执行 ln -s /data/var/lib/gitlab/data/.ssh .ssh 与 gitlab 容器共享 .ssh 下面的内容。为了在宿主机器上可以使用 git 账户无密码登录到 gitlab 容器中，需要创建在 .ssh 目录中添加一组密钥，并且把公钥加到 .ssh/authorized_keys 文件中去。\n在宿主机器上执行:\nssh-keygen -t rsa -P \u0026#39;\u0026#39; cat ~/.ssh/id_rsa.pub \u0026gt;\u0026gt; ~/.ssh/authorized_keys 添加 no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty 到 ~/.ssh/authorized_keys 所在的行首，结果看起来是这样的：\nno-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-rsa AAAAB3NzaC1yc2EAAA......CzGuj git@cn-bj-aliyun-3 登录测试一下\nssh -p 8022 127.0.0.1 观察到 ~/.ssh/authorized_keys 其它行都是这样\ncommand=\u0026#34;/opt/gitlab/embedded/service/gitlab-shell/bin/gitlab-shell key-32\u0026#34;,no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-rsa AAAAB3NzaC1yc2EAA......hOtpAl7J 在宿主机看来，每次 git 账户进行操作是都会执行 /opt/gitlab/embedded/service/gitlab-shell/bin/gitlab-shell, 我们恰好可以使用这个脚本来实现转发。\nmkdir -p /opt/gitlab/embedded/service/gitlab-shell/bin/ touch gitlab-shell chmod +x gitlab-shell 并且输入以下内容到 /opt/gitlab/embedded/service/gitlab-shell/bin/gitlab-shell 中\n#!/bin/sh ssh -p 8022 -o StrictHostKeyChecking=no git@127.0.0.1 \u0026#34;SSH_ORIGINAL_COMMAND=\\\u0026#34;$SSH_ORIGINAL_COMMAND\\\u0026#34; $0 $@\u0026#34; 一切就绪了， 可以使用 https://gitlab.example.com/repo.git 或者 git@gitlab.example.com:repo.git 这样的 URL 了。\n使用 SMTP 方式配置通知邮箱(腾讯企业邮箱) Gitlab SMTP 文档参考这里\ngitlab_rails[\u0026#39;smtp_enable\u0026#39;] = true gitlab_rails[\u0026#39;smtp_address\u0026#39;] = \u0026#34;smtp.exmail.qq.com\u0026#34; gitlab_rails[\u0026#39;smtp_port\u0026#39;] = 465 gitlab_rails[\u0026#39;smtp_user_name\u0026#39;] = \u0026#34;xxxx@xx.com\u0026#34; gitlab_rails[\u0026#39;smtp_password\u0026#39;] = \u0026#34;password\u0026#34; gitlab_rails[\u0026#39;smtp_authentication\u0026#39;] = \u0026#34;login\u0026#34; gitlab_rails[\u0026#39;smtp_enable_starttls_auto\u0026#39;] = true gitlab_rails[\u0026#39;smtp_tls\u0026#39;] = true gitlab_rails[\u0026#39;gitlab_email_from\u0026#39;] = \u0026#39;xxxx@xx.com\u0026#39; gitlab_rails[\u0026#39;smtp_domain\u0026#39;] = \u0026#34;exmail.qq.com\u0026#34; 改写默认的项目标签(Labels) 添加丰富的标准，方便进行项目管理。修改 issues_labels.rb 文件\nlabels = [ {\u0026#34;title\u0026#34;: \u0026#34;优先级:P0(紧急)\u0026#34;, \u0026#34;color\u0026#34;: \u0026#34;#E99695\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;立即处理\u0026#34;}, {\u0026#34;title\u0026#34;: \u0026#34;优先级:P1(高)\u0026#34;, \u0026#34;color\u0026#34;: \u0026#34;#E99695\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;优先处理\u0026#34;}, {\u0026#34;title\u0026#34;: \u0026#34;优先级:P2(中)\u0026#34;, \u0026#34;color\u0026#34;: \u0026#34;#E99695\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;有时间再处理\u0026#34;}, {\u0026#34;title\u0026#34;: \u0026#34;优先级:P3(低)\u0026#34;, \u0026#34;color\u0026#34;: \u0026#34;#E99695\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;暂不处理\u0026#34;}, {\u0026#34;title\u0026#34;: \u0026#34;分类:BUG\u0026#34;, \u0026#34;color\u0026#34;: \u0026#34;#D4C5F9\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;发现的BUG\u0026#34;}, {\u0026#34;title\u0026#34;: \u0026#34;分类:功能增强\u0026#34;, \u0026#34;color\u0026#34;: \u0026#34;#D4C5F9\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;增强已有的功能，属于优化的环节\u0026#34;}, {\u0026#34;title\u0026#34;: \u0026#34;分类:功能完善\u0026#34;, \u0026#34;color\u0026#34;: \u0026#34;#D4C5F9\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;完善功能\u0026#34;}, {\u0026#34;title\u0026#34;: \u0026#34;分类:文档修改\u0026#34;, \u0026#34;color\u0026#34;: \u0026#34;#D4C5F9\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;只是做文档修改\u0026#34;}, {\u0026#34;title\u0026#34;: \u0026#34;分类:新功能\u0026#34;, \u0026#34;color\u0026#34;: \u0026#34;#D4C5F9\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;新的功能和需求\u0026#34;}, {\u0026#34;title\u0026#34;: \u0026#34;项目:已上线\u0026#34;, \u0026#34;color\u0026#34;: \u0026#34;#C5DEF5\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;已发布上线\u0026#34;}, {\u0026#34;title\u0026#34;: \u0026#34;项目:已排期\u0026#34;, \u0026#34;color\u0026#34;: \u0026#34;#C5DEF5\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;已经安排了开发时间milestone\u0026#34;}, {\u0026#34;title\u0026#34;: \u0026#34;项目:已确认\u0026#34;, \u0026#34;color\u0026#34;: \u0026#34;#C5DEF5\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;功能已经确认，后续进行排期\u0026#34;}, {\u0026#34;title\u0026#34;: \u0026#34;项目:延后\u0026#34;, \u0026#34;color\u0026#34;: \u0026#34;#C5DEF5\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;功能无法确定是否开发，延期处理\u0026#34;}, {\u0026#34;title\u0026#34;: \u0026#34;项目:开发中\u0026#34;, \u0026#34;color\u0026#34;: \u0026#34;#C5DEF5\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;功能正在开发\u0026#34;}, {\u0026#34;title\u0026#34;: \u0026#34;项目:待讨论\u0026#34;, \u0026#34;color\u0026#34;: \u0026#34;#C5DEF5\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;需求已经提出，但是需要讨论是否需要开发\u0026#34;}, {\u0026#34;title\u0026#34;: \u0026#34;项目:测试中\u0026#34;, \u0026#34;color\u0026#34;: \u0026#34;#C5DEF5\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;功能已经完成开发，正在测试\u0026#34;} ] 总结 使用 Docker 方式安装 Gitlab 部署过程很简单，但是想达到一个理想的配置状况还是挺繁琐的，Docker 并不是治疗百病的良药，打铁还得自身硬。\n","permalink":"https://lewang.dev/posts/2018-12-18-gitlab-docker-install/","summary":"为什么要写这篇文章？ 曾经几年前在 Docker 还没有广泛应用的时候，在公司使用过源码的方式安装和升级过 Gitlab，虽远没有 Docker 方便，因为自己对 Linux 系统的理解，所以整体上感觉还是挺简单的。这几年随着 Docker 的普及，使得安装 Gitlab 更加的容易，不仅方便了我这样的老鸟，也更方便了小白用户们。但是 Gitlab 官方的 Docker 安装文档并没有写得很完善, 除了官方文档之外，检索出来的安装文档也是人云亦云，东拼西凑，结果也就是能运行起来，凑合着能用而已。避免出现下图中“我已经用 Docker 部署好啦”，其实已经翻车的情形。\n我希望每做一件小事的时候也都能抱着“知其然知其所以然”的心态对待，用 Docker 方式安装 Gitlab，说简单来说就是一行命令的事儿，但是这样就够了吗？我看是不够的，所以就有了这篇文档。\n本文需要达成的事项 在 CentOS 7 系统中安装 Docker 使用 Docker 方式安装中文版 Gitlab 和宿主机器共用 22(SSH) 端口 支持 SSH(22)/HTTPS(443) 方式推拉仓库 使用 SMTP 方式配置通知邮箱(腾讯企业邮箱) 改写默认的项目标签(Labels) 在 CentOS 7 系统中安装 Docker 这部分参考 Docker 的官方文档, 罗列一下安装步骤, 细节请看 Docker 的官方文档。如果使用 root 用户安装，sudo 可以去掉。\n1. 删除老版本 Docker $ sudo yum remove docker \\ docker-client \\ docker-client-latest \\ docker-common \\ docker-latest \\ docker-latest-logrotate \\ docker-logrotate \\ docker-selinux \\ docker-engine-selinux \\ docker-engine 2.","title":"使用 Docker 方式安装 Gitlab，没你想得那么简单"},{"content":"好久没有写过文字了，前几篇文也是自己从 Google Doc 中摘出来的工作日志，拿来凑凑数，刷新下 Github 的时间线，有点儿不太像话。看看自己书桌上叠起来超过一尺高的新书才知道，想看的还没来得及看完，想学的也还没来得及学会，想做的也没有做得很好，一年就过去了。2017 是我来魔都的第 5 年，回想这 5 年，自己的成长还是很多的，但 2017 这一年，好像与 2016 并没有什么大的不同，这让自己感到很担忧。\n在 2017 年的最后几天，终于离开了自己熟悉的工作环境，从自己的舒适空间加入了一个新的环境，我想这就是改变的开始，一个新的起点。\n2017 是平淡的，可能和未来的每一年也不会有太大差别，尽管我不希望这样。元旦假期这 3 天有空的时候都在回想，但是又想不起有那些特别印象深刻的事情，以至于总结都不太好写下去。我想那不如就回想几个和自己有关系的场景，做一个清单好了：\nBye 2017 区块链和比特币 人工智能（AI） 物联网大数据 Insurtech 和 Fintech 关于个人职业发展的思考 Hello 2018 Bye 2017 编程语言学习 Golang：完成基本语法学习（官方文档和《Go 语言实战》），可以无障碍阅读 Golang 项目代码（《自己动手写 Java 虚拟机》），并且可手动完成简单 Golang 项目用 Python 重写，主要有 eureka client Kotlin：除协程外，完成官方文档中所有语言部分的学习内容，并使用 kotlin 开发了 2 个项目，其中一个是用 kotlin 重写了 Aprereo CAS 的核心功能 TypeScript：完成基本语法学习，目前没有实际应用的场景，希望从 2018 起，自己在需要写 JavaScript 的地方，全部用 TypeScript 来代替 书籍阅读 阅读量太少，希望在 2018 年里能增大阅读量。\n技术类 《图说区块链》 严格讲不算技术类书籍 《自己动手写 Java 虚拟机》 《Go 语言实战》 《贝叶斯思维》未读完 《Spark 高级数据分析》未读完 《垃圾回收的算法和实现》未读完 《机器学习》未读完 文艺类 《追风筝的人》 今年读到最长的一篇小说， 从作者笔下故事了解了阿富汗的人文历史，很是感动和惊讶。另外就是觉得译者也很厉害，从头读到尾，除了人名，没觉得是在读一本英文翻译过来的书 《灿烂千阳》未读完 《上帝掷骰子吗》 感觉写这本书的作者应该是个天才，搜了一下，很神秘的作者 《乡关何处》 看着也挺有意思的 《野火集》 读晚了，读的大陆删节版，我觉得每一个人都应该读下这本书 《自私的基因》 《创业者的窘境》 看完觉得最该读这本书的人应该是我老婆 还有就是身边的亲密好友或同学 《人类简史》未读完 Kindle 版，用手机看的，亚马逊真牛逼，让我买了一个 kindle 放在家里吃灰 开源技术 计划搭建一个开源物联网平台，还没有想好具体要做的事情，所以没有什么进展 整理了常用的 docker templates，docker 和 python 一样成为自己不可或缺的工具 家庭网络 升级了硬件和软件，让家里无线网络有了更好的覆盖，并且让所有家庭联网设备都可以自由的访问互联网 儿子最喜欢的应用是 Youtube Kids 区块链和比特币 不记得是哪一天了，大约是在几个月之前，和同事在兰州料理吃午饭，这段时间大家都在讨论 ICO，区块链，交易所的事情，我突然想起自己在 2013 年的时候曾经关注过比特币和区块链，并且那个时候买了一些瑞波币，于是回去查了一下，私钥和地址都还在，并且由此莫名其妙赚到了人生的“第一桶金”。\n区块链（主要是数字货币、交易所和 ICO）在 2017 这一年全面爆发，各行各业的创业者都在想如何跟区块链技术搭上边，用区块链行业里的话来讲：区块链技术就是未来。但是大家谈论的最多的还是代币或者加密货币交易，比特币它自身就带着商业模式，大概只需要一个 DEMO 就可以大获成功。\n我比较认同 Coinbase 他们的愿景之一：为全世界的“低端人口”提供金融服务。不过 Coinbase 他们却把中国用户抛弃了。要是谈论金融服务，那又得思考，什么样的金融服务才是我等“低端人口”需要的。大家都在号称在做“普惠金融”，所以看看目前各类公司都在做什么，便可略知一二，很多时候，别人比我们自己还了解我们自己。\n人工智能（AI） 我想 2017 年的每一个技术人都应该从各种媒体渠道听说过 AlphaGo 的事迹，仿佛至此之后，人工智能变得特别的火，铺天盖地都是 AI 将要取代人类的信息。在 2017 年最后一个月里，有幸参加了 Google Developer Day 2017（GDD2017）大会，当然大会主题基本也都是 AI/IOT/Bigdata 相关的东西。记得李飞飞演讲中的这句话：The AI has changed the world，and who will change the AI？是的，Tensorflow API 工程师们，得好好思考一番了。\n另外有件事，我本人在读研期间是研究数据库方向，最近 Jeff Dean 的论文 《The Case for Learned Index Structures》 把机器学习带到了数据库研究领域，我想很快，类似思路就会出现在计算机科学相关领域，一下子仿佛又打开了一扇窗的感觉。对于我来讲，目前我希望自己弄清楚人工智能，机器学习，深度学习这几个名词之间的关系是怎样的，不至于在茶余饭后瞎扯淡时不知道别人在讨论什么就行。\n物联网大数据 把物联网和大数据放在一起应该是一个超热的话题了。在这两个领域，常见情景下技术方案已经没有任何所谓的技术壁垒。各大云厂商（Google，AWS，MS，Aliyun 等等）都推出或者增强了自己的物联网平台以及配套设施，智能硬件门槛也变得非常的低，让创业者或者企业不用费太大力气就可以搭建数据采集平台。\n短板效应不在：除了厂商提供云服务外，自行搭建也不再是难题。各种开源软件和组件都已经经过了长期的验证，只要用好了这些软件或者服务，相当于全世界最牛的程序员都在为你“打工”。这也是我计划搭建开源物联网平台的初衷。\n2017 年，NB-IoT 的新闻也越来越多，万物相连的未来已经到来。\nInsurtech 和 Fintech 就我个人而言，头次把这两个词放在一起的是在一场面试上。我在前家公司工作的 4 年半时间里，公司发展得很慢，远远低于我的预期。从开始的智能硬件创业到最近（2017）的 Insurtech，业务虽然有转型，但是总能看到最初 UBI 车险的影子。保险是一个强监管的行业，里面水很深，但也到处都是机会。\n像金融服务一样，越来越多的人，在各种场景下都需要各类保险来增加自身抗风险的能力。2017 年是我深入了解保险业和保险科技，包括但不限于车险、运费险、健康险以及各类创新险的元年，也是我进入金融科技领域（互联网金融）的元年。\n到了今天，除了维持银行对我的用户等级的要求，一般情况下，我已经全部使用各类新兴的理财 APP 管理自己的血汗钱了，并且只需要在 APP 上简单的按几下，各类账户就自动开通，并且收益率很容易就可以跑赢 CPI。\n目前自己 2017 年自己用过的理财 APP（除银行/证券 APP 外） 有：\n理财通（在微信钱包九宫格里，总体年收益率 5%，组成：活期 74% + 基金 26% ） 陆金所（各类抵用券大概有几百块，总体年收益率大约有 7.6%, 组成：定期（企业融资） 85% + 活期 5% + 基金 10%） 挖财宝（刚刚开始用，目测第一个月投入 1w，大概会有 200 左右的收益） 行业总是在不断的发展和变化的，自己也在不断的成长。\n2018 年希望理财方面能增加股票（主要是美股和港股）和指数型基金比例，稍微提高下自己对风险的耐受度。\n关于个人职业发展的思考 前几天看到自己的母校天大发了文章列了 2012 年到 2017 年这 5 年学校的变化，我看完后大为感叹：明显这 5 年学校的发展变化大大超过了我待在学校的 8 年。跳出学校这个小群里，整个社会这 5 年也是变化特别大的：比如北上广深等城市房价的暴涨，车辆保有量的暴增；再看看看互联网行业：BAT 中 B 的掉队，以及无数新兴行业颠覆者的出现。自己虽身在其中却只能作为浪潮中一个好不起眼的存在，去看着别人战火烽烟，我希望自己也能真正的进入战场，有对手（或者别人把你当对手）拼个你死我活（一同成长）。\n带着这样的天马行空的梦想，自己从毕业到现在一直都在很小，甚至是微小的创业公司工作。刚好前几天在朋友圈看到一篇讲职业规划的文章（牛人都需要职场规划指导，何况平凡如你我）, 自己看完后是有些感悟的。\n就这篇文章里来讲，我就是一个最好的反例。就像软件设计或者建筑设计一样，很多事情都是有范式的（或者模式），只要大家按照范式来做，一定不会差得太远。这件事，自己爱折腾的个性也就恰好体现出来了：我自以为范式是给一般人准备的，却没料想到自己其实就是一般人，而只是自己不太愿意承认而已。\n另外还有一个就是忽略了平台（大公司/明星公司）威力：一个好平台可能就是一条捷径或者是一个招牌。\n不过，我还是觉得选择是没有对和错的，也没有好和坏，只要知道自己要干什么就行。\nHello 2018 2018 希望自己能努力工作，努力学习，好好生活。\n","permalink":"https://lewang.dev/posts/2017-12-31-year-end-reviews-of-2017/","summary":"好久没有写过文字了，前几篇文也是自己从 Google Doc 中摘出来的工作日志，拿来凑凑数，刷新下 Github 的时间线，有点儿不太像话。看看自己书桌上叠起来超过一尺高的新书才知道，想看的还没来得及看完，想学的也还没来得及学会，想做的也没有做得很好，一年就过去了。2017 是我来魔都的第 5 年，回想这 5 年，自己的成长还是很多的，但 2017 这一年，好像与 2016 并没有什么大的不同，这让自己感到很担忧。\n在 2017 年的最后几天，终于离开了自己熟悉的工作环境，从自己的舒适空间加入了一个新的环境，我想这就是改变的开始，一个新的起点。\n2017 是平淡的，可能和未来的每一年也不会有太大差别，尽管我不希望这样。元旦假期这 3 天有空的时候都在回想，但是又想不起有那些特别印象深刻的事情，以至于总结都不太好写下去。我想那不如就回想几个和自己有关系的场景，做一个清单好了：\nBye 2017 区块链和比特币 人工智能（AI） 物联网大数据 Insurtech 和 Fintech 关于个人职业发展的思考 Hello 2018 Bye 2017 编程语言学习 Golang：完成基本语法学习（官方文档和《Go 语言实战》），可以无障碍阅读 Golang 项目代码（《自己动手写 Java 虚拟机》），并且可手动完成简单 Golang 项目用 Python 重写，主要有 eureka client Kotlin：除协程外，完成官方文档中所有语言部分的学习内容，并使用 kotlin 开发了 2 个项目，其中一个是用 kotlin 重写了 Aprereo CAS 的核心功能 TypeScript：完成基本语法学习，目前没有实际应用的场景，希望从 2018 起，自己在需要写 JavaScript 的地方，全部用 TypeScript 来代替 书籍阅读 阅读量太少，希望在 2018 年里能增大阅读量。\n技术类 《图说区块链》 严格讲不算技术类书籍 《自己动手写 Java 虚拟机》 《Go 语言实战》 《贝叶斯思维》未读完 《Spark 高级数据分析》未读完 《垃圾回收的算法和实现》未读完 《机器学习》未读完 文艺类 《追风筝的人》 今年读到最长的一篇小说， 从作者笔下故事了解了阿富汗的人文历史，很是感动和惊讶。另外就是觉得译者也很厉害，从头读到尾，除了人名，没觉得是在读一本英文翻译过来的书 《灿烂千阳》未读完 《上帝掷骰子吗》 感觉写这本书的作者应该是个天才，搜了一下，很神秘的作者 《乡关何处》 看着也挺有意思的 《野火集》 读晚了，读的大陆删节版，我觉得每一个人都应该读下这本书 《自私的基因》 《创业者的窘境》 看完觉得最该读这本书的人应该是我老婆 还有就是身边的亲密好友或同学 《人类简史》未读完 Kindle 版，用手机看的，亚马逊真牛逼，让我买了一个 kindle 放在家里吃灰 开源技术 计划搭建一个开源物联网平台，还没有想好具体要做的事情，所以没有什么进展 整理了常用的 docker templates，docker 和 python 一样成为自己不可或缺的工具 家庭网络 升级了硬件和软件，让家里无线网络有了更好的覆盖，并且让所有家庭联网设备都可以自由的访问互联网 儿子最喜欢的应用是 Youtube Kids 区块链和比特币 不记得是哪一天了，大约是在几个月之前，和同事在兰州料理吃午饭，这段时间大家都在讨论 ICO，区块链，交易所的事情，我突然想起自己在 2013 年的时候曾经关注过比特币和区块链，并且那个时候买了一些瑞波币，于是回去查了一下，私钥和地址都还在，并且由此莫名其妙赚到了人生的“第一桶金”。","title":"有关 2017 流水账和胡思乱想的记录"},{"content":"背景 阿里云云主机两块 100G 的云盘合一个逻辑卷（LV）来使用，单个的 100G 磁盘不够用，需要合在一起使用，并且方便以后扩容\n基本知识 磁盘 /dev/xvdb /dev/xvdc 分区, 使用 fdisk 进行分区 fdisk \u0026gt; n \u0026gt; p \u0026gt; 1..4, 主分区最多只有 4 个 准备分区后，将分区类型变为 LVM 分区，fdisk \u0026gt; t \u0026gt; 8e, 8e 是 LVM 类型 ID 物理卷（PV），卷组（VG），逻辑卷（LV），从磁盘分区创建 PV，通过 PV 创建 VG 或者把 PV 加入已有的 VG，在 VG 上创建 LV，LV 看起来就是逻辑的上磁盘，使用和真实的磁盘没什么明显区别, 在 LV 上构建文件系统 创建 创建物理卷 pvcreate /dev/xvdb1，对分区进行操作 创建 vg0 卷组 vgcreate vg0 /dev/xvdb1 查看卷组 vgdisplay， 可以看到卷组有多大 在 vg0 上创建 data 逻辑卷 lvcreate -L 99G -n data vg0 或者 lvcreate -l 25556 -n data vg0 创建文件系统 mkfs.ext4 /dev/vg0/data 挂载到系统目录即可使用 mount /dev/vg0/data /mnt 或者在 /etc/fstab 添加一行，然后 mount -a 添加磁盘 创建物理卷 pvcreate /dev/xvdc1，对分区进行操作 添加到 vg0 卷组 vgextend vg0 /dev/xvdc1 扩容逻辑卷 lvextend -L +99G /dev/vg0/data, 可以通过 vgdispaly 查看卷组剩余空间有多少 扩容文件系统 resize2fs /dev/vg0/data, 不需要卸载逻辑卷，可以在线完成扩容 备注 过程中有一块磁盘已经在使用，需要 umount 后再加入 LVM，出现了 device is busy 的情况，系统提示使用 lsof 或者 fuser 查询哪些进程在使用这个磁盘或者目录，最后通过 fuser 查询到进程号后，kill -9 pid 后解决 fuser -m /mnt\n参考链接 Linux 下 LVM 的配置详解 Linux 命令大全 ","permalink":"https://lewang.dev/posts/2017-08-03-lvm-in-action/","summary":"背景 阿里云云主机两块 100G 的云盘合一个逻辑卷（LV）来使用，单个的 100G 磁盘不够用，需要合在一起使用，并且方便以后扩容\n基本知识 磁盘 /dev/xvdb /dev/xvdc 分区, 使用 fdisk 进行分区 fdisk \u0026gt; n \u0026gt; p \u0026gt; 1..4, 主分区最多只有 4 个 准备分区后，将分区类型变为 LVM 分区，fdisk \u0026gt; t \u0026gt; 8e, 8e 是 LVM 类型 ID 物理卷（PV），卷组（VG），逻辑卷（LV），从磁盘分区创建 PV，通过 PV 创建 VG 或者把 PV 加入已有的 VG，在 VG 上创建 LV，LV 看起来就是逻辑的上磁盘，使用和真实的磁盘没什么明显区别, 在 LV 上构建文件系统 创建 创建物理卷 pvcreate /dev/xvdb1，对分区进行操作 创建 vg0 卷组 vgcreate vg0 /dev/xvdb1 查看卷组 vgdisplay， 可以看到卷组有多大 在 vg0 上创建 data 逻辑卷 lvcreate -L 99G -n data vg0 或者 lvcreate -l 25556 -n data vg0 创建文件系统 mkfs.","title":"LVM 实战记录"},{"content":"背景 微服务，日志分散且种类多（php/java/python），用 docker 起应用，日志通过卷放在宿主机器指定目录下，服务有众多实例，metrics 数据也不仅相同，无论是日志还是 metrics 数据，都可以看作是时间序列数据\n分散主要表现为：\n多个主机 多个目录下多个文件 应用开发所使用的技术栈不同日志格式不同 web log（主要是 nginx） 各类事件 一些其它事务性的日志 日志为时间序列数据，包括：\n系统日志: 各类系统产生的跟业务有关的日志或者与业务无关的日志 web 服务器日志：如 access.log/error.log 等有固定格式的日志 性能监控日志：打点记录各类服务的 metrics(全部为数值类型 long/double/bool) 系统日志 由时间戳、一些枚举值以及日志内容(变长字符串)组成\n日志时间颗粒度：支持毫秒/秒 枚举值包括： [必选]主机名/host [必选]服务名/service [必选]实例编号/instance [必选]日记级别/level：info/debug/warn/trace/error 等 [可选]异常名/exception: 如果是异常，把异常名作为枚举值记录 [可选]线程名/thread: [可选]方法名/method： [可选]文件名/file： [可选]行号/line： 日志内容(变长字符串): 为实际记录的内容以及异常堆栈信息 web 服务器日志 access log（nginx） 日志内容：主要是文本(string）或者一些系统 metrics 数据(数值类型 long/double)\n日志存储和处理： 数据磁带（1 周）：kafka 提供热数据检索（1 个月）：solr(or lucence based on cassandra) 日志存储（永久）： kariosdb/cassandra: 支持 double/long/string 类型，kariosdb 相当于在 cassandra 上面套了一个壳，这样简化了很多时间序列数据处理的操作 数据展示：grafana，官方支持 kariosdb 扩展：数据深度挖掘分析 系统架构 特点：\n服务现在，面向未来的架构（每层可单独升级或者替换技术栈） 低入侵，原有系统日志不需要改造，使用 flume agent 收集 整个系统无单点，保证可靠性 层次清晰，好扩展，无论 Flume，kafka 还是 cassandra 都方便扩容，存储层使用 kafka 滚动存储数据，cassandra 存储所有日志数据和 metrics 利用现有社区活跃的开源产品构建，除了 kalka 引入了 zk 外，没有其它依赖，整个存储层可以结合现有的资源 其它方案 ELK ELK 技术栈在整体架构上与前面的比较接近（几乎差不多），将会引入数个与目前系统没有太大联系的产品（E/L），前面的架构不仅用于日志的处理，并且可扩展支持处理所有的事件（数据流）处理，更符合目前业务。ELK 优点是拆箱可用，经过一段时间的试用，目前是放弃状态\n其它非 JVM 解决方案没有在考虑范围之类，包括但不限于： scribe https://github.com/facebookarchive TICK Stack https://www.influxdata.com/time-series-platform/, 比较适合收集服务器和各类服务的 metrics 数据，配合 grafana，可以替代 zabbix 的工作，其中日志存储（output）默认使用 influxdb，influxdb 目前开源只有单机版，但 telegraf 官方支持 output 到 cassandra/kafka/rabbit 等，所以存储也不是问题。前端可以 grafana（支持 kariosdb/influxdb/mysql/opentsdb 等作为数据源）。 rsync 直接同步日志到指定机器，然后在机器上集中处理，使用 mysql/postgresql 等存储日志，目前的量是可以的，但是也需要做不少开发工作 ","permalink":"https://lewang.dev/posts/2017-07-23-log-processing-system/","summary":"背景 微服务，日志分散且种类多（php/java/python），用 docker 起应用，日志通过卷放在宿主机器指定目录下，服务有众多实例，metrics 数据也不仅相同，无论是日志还是 metrics 数据，都可以看作是时间序列数据\n分散主要表现为：\n多个主机 多个目录下多个文件 应用开发所使用的技术栈不同日志格式不同 web log（主要是 nginx） 各类事件 一些其它事务性的日志 日志为时间序列数据，包括：\n系统日志: 各类系统产生的跟业务有关的日志或者与业务无关的日志 web 服务器日志：如 access.log/error.log 等有固定格式的日志 性能监控日志：打点记录各类服务的 metrics(全部为数值类型 long/double/bool) 系统日志 由时间戳、一些枚举值以及日志内容(变长字符串)组成\n日志时间颗粒度：支持毫秒/秒 枚举值包括： [必选]主机名/host [必选]服务名/service [必选]实例编号/instance [必选]日记级别/level：info/debug/warn/trace/error 等 [可选]异常名/exception: 如果是异常，把异常名作为枚举值记录 [可选]线程名/thread: [可选]方法名/method： [可选]文件名/file： [可选]行号/line： 日志内容(变长字符串): 为实际记录的内容以及异常堆栈信息 web 服务器日志 access log（nginx） 日志内容：主要是文本(string）或者一些系统 metrics 数据(数值类型 long/double)\n日志存储和处理： 数据磁带（1 周）：kafka 提供热数据检索（1 个月）：solr(or lucence based on cassandra) 日志存储（永久）： kariosdb/cassandra: 支持 double/long/string 类型，kariosdb 相当于在 cassandra 上面套了一个壳，这样简化了很多时间序列数据处理的操作 数据展示：grafana，官方支持 kariosdb 扩展：数据深度挖掘分析 系统架构 特点：","title":"日志收集和分析系统架构"},{"content":"背景 Java 生态下的日志库太多，配置也不同，大多数情况下会使用 SLF4j (又引入了一个库)来抽象日志接口。在使用 Log4j2 后，发现可以不使用 SLF4j 了，并且配置变得更简单，可以使用 lombok 的 log4j2 注解等。\n需要搞清楚\n如何设置哪些日志要记录下来 日志记录到哪里去 LEVEL 日志级别： 内置（有 Fatal）：All \u0026lt; Trace \u0026lt; Debug \u0026lt; Info \u0026lt; Warn \u0026lt; Error \u0026lt; Fatal \u0026lt; OFF 还可以自定义： https://logging.apache.org/log4j/2.0/manual/customloglevels.html Appender: 日志输出的目的地 内置的目的地有：\nconsole, files（FIle/RollingFile）, remote socket servers, Apache Flume, JMS, remote UNIX Syslog daemons, various database APIs \u0026hellip; Appender 在接受到日志以后，可以通过级别过滤选择记录日志，具体配置： https://logging.apache.org/log4j/2.0/manual/appenders.html\nLogger 负责决定哪些日志要记录和发配日志 哪些需要记日志，设置什么级别，并且配置日志输出到哪些个 Appender 中去都在 Logger 中配置。Logger 有类似继承的关系，名为 Root 的的 Logger 为所有 Logger 的根，也就是说没有做特殊设置（additivity=false), 那么这个 Logger 的日志将会记录到自己指定的 Appender，并且也都会记录到自己所有“父” Logger 设置的 Appender 中去\n继承关系由 Logger 的名称推断出来，名称可以认为是 Logger 树的前序遍历打印的节点路径。\nLogger 定义\nLogger Name 将会记录日志的 Logger Root Root X Root, X X.Y Root, X, Y X.Z Root, X,Z M Root, M Filter 日志过滤器 DENY,ACCEPT,NEUTRAL 三个选项，所有进入当前 Appender 的所有日志，Filter 可以设置来选择日志是否要记录\n","permalink":"https://lewang.dev/posts/2017-07-15-log4j2/","summary":"背景 Java 生态下的日志库太多，配置也不同，大多数情况下会使用 SLF4j (又引入了一个库)来抽象日志接口。在使用 Log4j2 后，发现可以不使用 SLF4j 了，并且配置变得更简单，可以使用 lombok 的 log4j2 注解等。\n需要搞清楚\n如何设置哪些日志要记录下来 日志记录到哪里去 LEVEL 日志级别： 内置（有 Fatal）：All \u0026lt; Trace \u0026lt; Debug \u0026lt; Info \u0026lt; Warn \u0026lt; Error \u0026lt; Fatal \u0026lt; OFF 还可以自定义： https://logging.apache.org/log4j/2.0/manual/customloglevels.html Appender: 日志输出的目的地 内置的目的地有：\nconsole, files（FIle/RollingFile）, remote socket servers, Apache Flume, JMS, remote UNIX Syslog daemons, various database APIs \u0026hellip; Appender 在接受到日志以后，可以通过级别过滤选择记录日志，具体配置： https://logging.apache.org/log4j/2.0/manual/appenders.html\nLogger 负责决定哪些日志要记录和发配日志 哪些需要记日志，设置什么级别，并且配置日志输出到哪些个 Appender 中去都在 Logger 中配置。Logger 有类似继承的关系，名为 Root 的的 Logger 为所有 Logger 的根，也就是说没有做特殊设置（additivity=false), 那么这个 Logger 的日志将会记录到自己指定的 Appender，并且也都会记录到自己所有“父” Logger 设置的 Appender 中去","title":"Log4j2 快速入门"},{"content":"背景 目前公司主要服务都是直接使用 MySQL 主服务器，从服务主要给离线数据分析服务使用，由于前期弄得比较简单的粗暴，从服务上还有一两个数据库在做生产使用, 并且从服数据已经不能和主服进行进行同步了，有大量错误，忽略都没有办法进行。此外，主服仅配置了三个核心数据的 binlog，随着业务的变化，其它数据库不能走主从这条路来同步数据，于是希望不停机的情形下重新调整主服配置，记录所有的数据库的 binlog，同时添加新的从服务器来同步数据\n方案 1 MySQL 的主从是通过同步 binlog 日志来实现数据同步的，于是需要想办法把从服数据先于主服同步，记录 binlog 的 pos 值，再配置从服从该 pos 处开始同步，考虑可以使用 mysqldump 导出所有 innodb 数据，使用 rsync 同步所有 myisam 数据文件，然后再开启主从同步。但是目前这种方案不适用，主服不能长时间停机\n方案 2 使用 xtrabackup 来完成目标\n主从服务器上都需要安装 xtrabackup（实际使用 xtrabackupex）：\nyum install http://www.percona.com/downloads/percona-release/redhat/0.1-4/percona-release-0.1-4.noarch.rpm yum install -y percona-xtrabackup-24 主服： 备份数据 # 注意数据库名称的转义，例如 - 号是 @002d innobackupex --defaults-file=/etc/my.cnf --socket=/var/lib/mysql/mysql.sock --user=root --password=xxxx --parallel=2 --databases=\u0026#34;db1 db2\u0026#34; /data/backup/xtrabackup/ 保持事务一致 innobackupex --apply-log /data/backup/xtrabackup/2017-06-06_13-16-21/ 同步数据 rsync -avHz 2017-06-06_13-16-21 sysops@cow:/data/backup 从服： 修改 my.cnf 相关配置，恢复备份数据\n# 关闭 MySQL Servier 后恢复数据 innobackupex --defaults-file=/etc/my.cnf --copy-back /data/backup/2017-06-06_13-16-21 修改数据权限\nchown -R mysql.mysql mysql 开启从服，登录 msyql 添加主从配置，并开启同步\n# Master 上执行 GRANT REPLICATION SLAVE ON *.* TO \u0026#39;repl\u0026#39;@\u0026#39;10.23.9.146\u0026#39; IDENTIFIED BY \u0026#39;password\u0026#39;; # Slave 上执行 [root@bull data]# cat backup/2018-04-26_21-27-31/xtrabackup_binlog_info mysql-bin.000058 103394265 CHANGE MASTER TO MASTER_HOST=\u0026#39;10.4.25.28\u0026#39;, MASTER_USER=\u0026#39;repl\u0026#39;, MASTER_PASSWORD=\u0026#39;password\u0026#39;, MASTER_PORT=3306, MASTER_LOG_FILE=\u0026#39;mysql-bin.000058\u0026#39;, MASTER_LOG_POS=103394265; 检查主从状态 show master status; show slave status\\G; 参考 [percona-xtrabackup] (https://www.percona.com/doc/percona-xtrabackup/LATEST/index.html) [wsgzao.github.io/post/xtrabackup] (https://wsgzao.github.io/post/xtrabackup/) ","permalink":"https://lewang.dev/posts/2017-06-06-xtrabackup/","summary":"背景 目前公司主要服务都是直接使用 MySQL 主服务器，从服务主要给离线数据分析服务使用，由于前期弄得比较简单的粗暴，从服务上还有一两个数据库在做生产使用, 并且从服数据已经不能和主服进行进行同步了，有大量错误，忽略都没有办法进行。此外，主服仅配置了三个核心数据的 binlog，随着业务的变化，其它数据库不能走主从这条路来同步数据，于是希望不停机的情形下重新调整主服配置，记录所有的数据库的 binlog，同时添加新的从服务器来同步数据\n方案 1 MySQL 的主从是通过同步 binlog 日志来实现数据同步的，于是需要想办法把从服数据先于主服同步，记录 binlog 的 pos 值，再配置从服从该 pos 处开始同步，考虑可以使用 mysqldump 导出所有 innodb 数据，使用 rsync 同步所有 myisam 数据文件，然后再开启主从同步。但是目前这种方案不适用，主服不能长时间停机\n方案 2 使用 xtrabackup 来完成目标\n主从服务器上都需要安装 xtrabackup（实际使用 xtrabackupex）：\nyum install http://www.percona.com/downloads/percona-release/redhat/0.1-4/percona-release-0.1-4.noarch.rpm yum install -y percona-xtrabackup-24 主服： 备份数据 # 注意数据库名称的转义，例如 - 号是 @002d innobackupex --defaults-file=/etc/my.cnf --socket=/var/lib/mysql/mysql.sock --user=root --password=xxxx --parallel=2 --databases=\u0026#34;db1 db2\u0026#34; /data/backup/xtrabackup/ 保持事务一致 innobackupex --apply-log /data/backup/xtrabackup/2017-06-06_13-16-21/ 同步数据 rsync -avHz 2017-06-06_13-16-21 sysops@cow:/data/backup 从服： 修改 my.cnf 相关配置，恢复备份数据","title":"MySQL 数据备份和不停机重新搭建主从同步"},{"content":" 政策已变，目前只能办理有效期为 15 天的临牌，并且最多只能办理两次\n问题 外地户口，上海购车办理机动车(出省)临牌(出省和不出省的临牌办理方法一样,出省临牌有效期是30天，不出省的是15天)\n文档记录状态 可用\n发生时间 2016年01月28日9:00至9:30\n体验得分 90\n办理地点 黄浦公安分局交警支队，地址上海黄浦区陆家浜路88号（地铁4号线南浦大桥站1号出口200米处）\n可办理时间 周一至周六 8:30-17:00\n需要材料 车辆合格证原件和复印件 购车发票原件和复印件 交强险保单原件和复印件 身份证原件和正反面复印件 零钱5元 办理流程记录 进入大厅后跟保安说办理机动车临牌，拿一张《申请办理机动车临时号牌单》，然后坐在椅子上排队并填好临时号牌单 等待办理, 交警会打印两张临牌出来 恭喜你，没有第3步，可以拿着临牌走了 提高效率的小提示 自带黑色签字笔一支 原件和复印件分开成两份 ","permalink":"https://lewang.dev/posts/2016-01-28-banlinpai/","summary":" 政策已变，目前只能办理有效期为 15 天的临牌，并且最多只能办理两次\n问题 外地户口，上海购车办理机动车(出省)临牌(出省和不出省的临牌办理方法一样,出省临牌有效期是30天，不出省的是15天)\n文档记录状态 可用\n发生时间 2016年01月28日9:00至9:30\n体验得分 90\n办理地点 黄浦公安分局交警支队，地址上海黄浦区陆家浜路88号（地铁4号线南浦大桥站1号出口200米处）\n可办理时间 周一至周六 8:30-17:00\n需要材料 车辆合格证原件和复印件 购车发票原件和复印件 交强险保单原件和复印件 身份证原件和正反面复印件 零钱5元 办理流程记录 进入大厅后跟保安说办理机动车临牌，拿一张《申请办理机动车临时号牌单》，然后坐在椅子上排队并填好临时号牌单 等待办理, 交警会打印两张临牌出来 恭喜你，没有第3步，可以拿着临牌走了 提高效率的小提示 自带黑色签字笔一支 原件和复印件分开成两份 ","title":"办理机动车临牌"},{"content":"问题 外地户口＋上海居住证，外地驾照即将到期，需要将外地驾照转入上海并且进行驾照到期换证。\n可用状态 可用\n发生时间 2015年10月15日8:00至10:00\n体验得分 90\n办理地点 车辆管理所三分所，地址浦东新区沪南公路2638 号\n可办理时间 周一至周五 9:00-17:00\n办理流程记录 前往4号楼的3层填表缴费并拍照，缴费需要排队，人较多，费用 25 元 拍好照片并拿到照片和表格后前往4号楼2层填表缴费并体检，缴费需要排队，人较多，费用 60 元 缴完体检费进去之后，需要填表并且贴好照片，体检项都是分开进行，主要体检内容包括视力，听力，身高体重，色盲等检测，其他项都是医生随手填上去的，哪个人少就先去体检哪个。我体检的顺序依次是视力，听力，身高体重，色盲检测。 每项体检完，看下体检表格，看看体检项有没有空的，如果没有就直接去出口处，把表格交上去，然后等报告 拿到体检报告后，直接去1号楼一层大厅门口右手处工作人员拿号，号码那张纸要保存好，最后一步领证还需要使用。 叫号后前往柜台管理，跟工作人员确认下需要办理的内容。我这里是办理转入和到期换证。这里会使用照片一张，工作人员会提供剪刀。办理过程大约需要5分钟 前往缴费处缴费，注意看下缴费窗口的号码，缴费处在大厅另外一侧，费用 10 元 前往发证处等待叫号领证 提高效率的小提示 8点30之前到达4号楼3层排队, 尽管 9 点才开始 自带黑色签字笔一支 身份证复印件和居住证复印件各一份 ","permalink":"https://lewang.dev/posts/2015-10-15-huanzheng/","summary":"问题 外地户口＋上海居住证，外地驾照即将到期，需要将外地驾照转入上海并且进行驾照到期换证。\n可用状态 可用\n发生时间 2015年10月15日8:00至10:00\n体验得分 90\n办理地点 车辆管理所三分所，地址浦东新区沪南公路2638 号\n可办理时间 周一至周五 9:00-17:00\n办理流程记录 前往4号楼的3层填表缴费并拍照，缴费需要排队，人较多，费用 25 元 拍好照片并拿到照片和表格后前往4号楼2层填表缴费并体检，缴费需要排队，人较多，费用 60 元 缴完体检费进去之后，需要填表并且贴好照片，体检项都是分开进行，主要体检内容包括视力，听力，身高体重，色盲等检测，其他项都是医生随手填上去的，哪个人少就先去体检哪个。我体检的顺序依次是视力，听力，身高体重，色盲检测。 每项体检完，看下体检表格，看看体检项有没有空的，如果没有就直接去出口处，把表格交上去，然后等报告 拿到体检报告后，直接去1号楼一层大厅门口右手处工作人员拿号，号码那张纸要保存好，最后一步领证还需要使用。 叫号后前往柜台管理，跟工作人员确认下需要办理的内容。我这里是办理转入和到期换证。这里会使用照片一张，工作人员会提供剪刀。办理过程大约需要5分钟 前往缴费处缴费，注意看下缴费窗口的号码，缴费处在大厅另外一侧，费用 10 元 前往发证处等待叫号领证 提高效率的小提示 8点30之前到达4号楼3层排队, 尽管 9 点才开始 自带黑色签字笔一支 身份证复印件和居住证复印件各一份 ","title":"驾照转入换证"},{"content":" 这是一篇译文，原文链接：Tuning NGINX for Performance\nNginx 为人熟知的是在负载均衡、静态缓存和 WEB 服务器等方面的高性能，目前世界上最繁忙的站点中大约有 40% 在使用 Nginx。绝大多数情况下，大多数默认的 Nginx 和 Linux 配置都可以工作得非常好，但也需要做一些优化以获得最好的性能。本文将讨论在优化系统时需要考虑的 Nginx 和 Linux 的部分配置。可配置的选项有很多，但是本文只涵盖推荐大多数用户调整的配置选项。本文没有涵盖的配置选项，只有那些对 Nginx 和 Linux 有了深入的理解的人或者获得了 Nginx 技术支持和专业的服务团队的推荐建议后，才可以考虑调整。Nginx 专业服务器团队已经为世界上一些最繁忙的站点通过优化 Nginx 获得了最高水平的性能，并且可以为任何需要获得自己系统最大产出的客户服务。\n简介 本文假设读者对 Nginx 架构和配置的概念已有了基本的了解。Nginx 的文档内容将不会在本文中重复，但本文会提供各项配置简要的介绍和相关文档的链接。\n在性能调优时，要遵循一个好的规则：一次只修改一个配置选项，如果这个修改没有在性能方面带来优化，那么要再改回默认值。\n我们从 Linux 性能优化的讨论开始，因为 Linux 性能优化的一些值会影响到 Nginx 的一些配置。\nLinux 配置 尽管现代 Linux 内核（2.6+）在各种配置情况下都工作得很好，但也有一些配置是想要修改的。如果操作系统的配置设置的太低，那内核日志将会有错误信息，从而得知哪些配置需要调整。Linux 性能优化可能涉及的配置有很多，这里我们只讨论那些优化达到正常工作负载最有可能涉及到的那些配置。调整这些配置请参考详细的 Linux 文档。\nBacklog 队列 下面的配置选项与网络连接和其排队方法直接相关。如果连入率很高（译者注：客户端发起的连接很多）且系统性能配置不匹配，例如一些连接表现得有所停顿，那么修改下面得配置将可能有用。\nnet.core.somaxconn: 设置等待 Nginx 接受的连接队列的大小。由于 Nginx 接受连接非常的快，这个值通常情况下不用设置得很大，但系统默认值可能比较小，所以对于流量比较大的站点，增大这个值是个不错的想法。如果这个值太小，在内核日志中应该会看到错误消息，那么就需要增大这个值，直到错误消失。注意：若将这个值设置为大于 512 的话，那么需要在 Nginx 配置中修改 listen 指令的 backlog 参数来匹配这个数字。\nnet.core.netdev_max_backlog: 设置数据包在被发送到 CPU 前可被网卡缓存的速率。对于带宽很大的机器来说，这个值需要增大。可以查阅网卡关于这项设置的建议文档或者查看内核日志中此项设置相关的错误。\n文件描述符 文件描述符是用于处理例如连接和打开的文件等的操作系统资源。Nginx 在一个连接中使用文件描述符可以达到两个，例如 Nginx 做代理，那文件描述符一个用于客户端连接，另外一个用于代理服务器，但如果开启 HTTP 保持连接，那这个比例将会很低。对于一个连接数量很大很大的系统来说，这个值可能需要调整：\nsys.fs.file_max: 文件描述符的系统级限制。\nnofile: 文件描述符用户级限制，可以在 /etc/security/limits.conf 文件中修改。\n临时端口范围(Ephemeral ports) 当 Nginx 用作代理，每一个到后端服务器的连接都会短暂的、临时的使用一个端口。\nnet.ipv4.ip_local_port_range: 设置端口启始范围。如果观察到端口耗尽，那么需要增大这个范围。通常的端口范围设置是 1024 到 65000。\nnet.ipv4.tcp_fin_timeout: 设置端口停止使用后可再次被其它连接使用所需要的时间。通常默认值是 60 秒，但通常减少到 30 秒或者 15 秒都是安全的\nNginx 配置 下面是一些影响系统性能的 Nginx 指令。前面已经说过，本文只讨论一些推荐给大多数人调整的指令。其它没有提到的任何指令，若没有 Nginx 团队的建议，推荐不要修改。\n工作进程 Nginx 可以运行多个工作进程，每一个都可以处理大量的连接。通过下面的指令可以控制运行工作进程的数量和每个进程处理连接的数量：\nworker_processes:控制 Nginx 运行工作进程的数量。多数情况下，每一个 CPU 核运行一个工作进程的方式可以很好的工作。可以通过设置指令的值为 \u0026ldquo;auto\u0026rdquo; 来达到这个效果。但也有需要增大这个数字的时候，例如工作进程需要做很多的磁盘 IO 操作。默认值是 1。\nworker_connections:这个值表示每一个工作进程可以同时处理连接的最大数目。默认是 512，但是大多数系统可以处理一个大得多的数字。这个值应该被设置成多少依赖与服务器的大小和网络流量的特征。通过具体的测试可以找到具体的值。\n连接持久化(Keepalives) 连接持久化可以在创建和关闭连接过程中降低 CPU 和网络开销，从而可对性能产生较大的影响。Nginx 会终止所有客户端连接以及和客户端连接分离开且独立的后端服务器连接。Nginx 支持客户端连接和后端服务器连接的持久化，可以通过下面的指令设置客户端连接持久化：\nkeepalive_requests:一个客户端使用一个持久化连接发送的请求数，默认值是 100. 这个值可以设置得很大，尤其在在做压力测试过程中，单一客户端发送大量请求得情况下会特别有用。\nkeepalive_timeout: 连接一旦空闲后还会保持多长时间（也就是空闲多长时间以后被关闭）。\n下面的指令可以设置后端服务器连接的持久化：\nkeepalive: 为每一个工作进程开启的到后端服务器空闲持久化连接的数量。这个指令没有默认值。 为了启用 Nginx 到后端服务器的持久化连接，需要添加一下指令：\nproxy_http_version 1.1; proxy_set_header Connection \u0026ldquo;\u0026rdquo;; 访问日志 记录每个请求的访问日志会占用 CPU 和 I/O 周期，启用访问日志缓冲可以减小影响。启动访问日志缓冲会使得 Nginx 缓存一些列日志记录到缓存中，然后在同一时间将他们写到文件中，而不是分开的执行每一个写操作。启用访问日志缓存功能需要在 Nginx 配置文件的 access_log 指令中使用 \u0026ldquo;buffer=size\u0026rdquo; 选项。这个是设置将要使用的缓存大小。也可以设置 \u0026ldquo;flush=time\u0026rdquo; 选项来告诉 Nginx 多长时间来将缓存中的记录写到磁盘日志文件中。如果配置了这两个选项，那当日志放不进缓存（缓存满了）或者缓存中日志记录比 flush 参数设置的时间还要老时，Nginx 将会写日志记录到日志文件中。当工作进程重新打开日志文件或者工作进程被关闭时，日志记录都会被写到日志文件中。当然，完全关闭访问日志也是可以的。\nSendfile Sendfile 是一个可以在 Nginx 中使用的操作系统的特性。这个特性可以使 TCP 数据传输得更快：它通过在内核中从一个文件描述符拷贝数据到另一个文件描述符，通常可以达到零拷贝。 Nginx 可以利用它通过 socket 发送缓存的或者磁盘上的内容，并且不需要任何的用户空间的上下文切换，从而使得发送数据的过程非常的快并且使用更少的 CPU 开销。由于数据从不触及用户空间，在处理链中添加需要访问数据的过滤器是行不通的，所以无法使用任何会改变数据内容的 Nginx 过滤器，例如 gzip 过滤器。Sendfile 默认是不启用的。\n限制(Limits) Nginx 和 Nginx Plus 允许设置一些列的限制项，可用于控制被客户端访问的资源，所以这些设置项会对系统性能产生影响，并且也会影响用户体验和安全。下面是其中的一些指令：\nlimit_conn/limit_conn_zone:这两个指令可用于限制 Nginx 允许的连接数目，例如限制单一的客户端 IP 地址。可以阻止单个客户端建立过多连接消耗过多资源的情况发生。\nlimit_rate: 限制一个客户端单个连接的带宽大小。可以防止由特定的客户端造成系统高负载的情况发生，从而保证所有客户端都可以获得好质量的服务。\nlimit_req/limit_req_zone: 这两个指令可以限制被 Nginx 处理的请求速率。和 limit_rate 一起使用 可以防止由特性的客户端造成系统高负载情况的发生，从而保证所有客户端都可以获得好质量的服务。这些指令可以用于改善系统安全，尤其是在登录页面，可以通过设置限制请求速率值，这个值完全胜任一个人类用户但又回减慢程序用户访问来提高系统的安全。（大概意思就是通过这个设置来限制机器程序的访问, 如爬虫）\nmax_conns: 设置允许同时连接到后端服务器组中一台服务器的最大连接数。这个设置可以防止后端服务器过载。默认值是 0， 表示无限制。\nqueue: 如果设置了 max_conns, 并且当一个请求由于没有可用的后端服务器或者后端服务器已到达 max_conns 上限而不能被处理时，此时发生什么就会交给 queue 指令来管理。这个指令设置存放到队列中请求数以及请求等待超时时间。如果这个指令没有设置，请求将不会出现排队的情形。\n附加配置 值得一提的是，Nginx 有一些附加的特性可以用来提高 Web 应用的性能，尽管他们不是真的属于调优的范畴，但对性能的影响也是显著的。下面将讨论其中的两个特性。\n缓存 有一个用于负载均衡一组后端 Web 应用的 Nginx 实例，通过启用这个实例的缓存配置，将会显著的增加到客户端的响应时间，同时也会显著的降低后端服务器的负载。缓存本身就是一个主题，这里不再详细讨论。需要了解更多的关于配置 Nginx 缓存的信息，可以参考 Nginx 管理指南 - 缓存篇\n压缩 压缩返回给客户端的数据可以大大减小返回数据的大小，从而需求更少的带宽，但压缩的操作需要占用 CPU 资源。所以在减少带宽有价值时使用压缩才是最好的。值得注意的还有不要压缩已经压缩过的对象， 譬如 jpeg 图片等。需要了解更多的关于配置 Nginx 压缩的信息，可以参考Nginx 管理指南 - 压缩篇\n更多阅读 Nginx 性能测试 Nginx 文档 Nginx 和 Nginx Plus 特性 Nginx Plus 技术规范 ","permalink":"https://lewang.dev/posts/2014-10-16-tuning-nginx/","summary":"这是一篇译文，原文链接：Tuning NGINX for Performance\nNginx 为人熟知的是在负载均衡、静态缓存和 WEB 服务器等方面的高性能，目前世界上最繁忙的站点中大约有 40% 在使用 Nginx。绝大多数情况下，大多数默认的 Nginx 和 Linux 配置都可以工作得非常好，但也需要做一些优化以获得最好的性能。本文将讨论在优化系统时需要考虑的 Nginx 和 Linux 的部分配置。可配置的选项有很多，但是本文只涵盖推荐大多数用户调整的配置选项。本文没有涵盖的配置选项，只有那些对 Nginx 和 Linux 有了深入的理解的人或者获得了 Nginx 技术支持和专业的服务团队的推荐建议后，才可以考虑调整。Nginx 专业服务器团队已经为世界上一些最繁忙的站点通过优化 Nginx 获得了最高水平的性能，并且可以为任何需要获得自己系统最大产出的客户服务。\n简介 本文假设读者对 Nginx 架构和配置的概念已有了基本的了解。Nginx 的文档内容将不会在本文中重复，但本文会提供各项配置简要的介绍和相关文档的链接。\n在性能调优时，要遵循一个好的规则：一次只修改一个配置选项，如果这个修改没有在性能方面带来优化，那么要再改回默认值。\n我们从 Linux 性能优化的讨论开始，因为 Linux 性能优化的一些值会影响到 Nginx 的一些配置。\nLinux 配置 尽管现代 Linux 内核（2.6+）在各种配置情况下都工作得很好，但也有一些配置是想要修改的。如果操作系统的配置设置的太低，那内核日志将会有错误信息，从而得知哪些配置需要调整。Linux 性能优化可能涉及的配置有很多，这里我们只讨论那些优化达到正常工作负载最有可能涉及到的那些配置。调整这些配置请参考详细的 Linux 文档。\nBacklog 队列 下面的配置选项与网络连接和其排队方法直接相关。如果连入率很高（译者注：客户端发起的连接很多）且系统性能配置不匹配，例如一些连接表现得有所停顿，那么修改下面得配置将可能有用。\nnet.core.somaxconn: 设置等待 Nginx 接受的连接队列的大小。由于 Nginx 接受连接非常的快，这个值通常情况下不用设置得很大，但系统默认值可能比较小，所以对于流量比较大的站点，增大这个值是个不错的想法。如果这个值太小，在内核日志中应该会看到错误消息，那么就需要增大这个值，直到错误消失。注意：若将这个值设置为大于 512 的话，那么需要在 Nginx 配置中修改 listen 指令的 backlog 参数来匹配这个数字。\nnet.core.netdev_max_backlog: 设置数据包在被发送到 CPU 前可被网卡缓存的速率。对于带宽很大的机器来说，这个值需要增大。可以查阅网卡关于这项设置的建议文档或者查看内核日志中此项设置相关的错误。","title":"Nginx 性能调优「译」"},{"content":"这几天每次 Push 博客到 Github Pages 时总会收到一封邮件：\nThe page build completed successfully, but returned the following warning:\nGitHub Pages recently underwent some improvements (https://github.com/blog/1715-faster-more-awesome-github-pages) to make your site faster and more awesome, but we\u0026rsquo;ve noticed that iforget.info isn\u0026rsquo;t properly configured to take advantage of these new features. While your site will continue to work just fine, updating your domain\u0026rsquo;s configuration offers some additional speed and performance benefits. Instructions on updating your site\u0026rsquo;s IP address can be found at https://help.github.com/articles/setting-up-a-custom-domain-with-github-pages#step-2-configure-dns-records, and of course, you can always get in touch with a human at support@github.com. For the more technical minded folks who want to skip the help docs: your site\u0026rsquo;s DNS records are pointed to a deprecated IP address.\nFor information on troubleshooting Jekyll see:\nhttps://help.github.com/articles/using-jekyll-with-pages#troubleshooting\nIf you have any questions please contact us at https://github.com/contact.\n大概意思就是我们 Github Pages 服务最近做了升级，这个升级将会使你的博客访问起来快到碉堡了，赶快来配置(修改域名记录就可以)一下用上吧。\n之前 iforget.info 这个域名有两条记录：\nA 记录： iforget.info 到 207.97.227.245\nCNAME(Alias) 记录：www.iforget.info 到 iforget.info\n207.97.227.245 是个美帝的 IP，所以速度比较慢。我还想着给 iforget.info 备个案，从此用上七牛的 CND 来加速了。好消息来了，Github Pages 支持全球 CDN 了，赶紧修改一下 DNS 记录(把之前的记录都删掉吧)：\nCNAME 记录: iforget.info 到 thisiswangle.github.io\nCNAME 记录: www.iforget.info 到 thisiswangle.github.io\n好了，静候 DNS 生效。\n➜ ~ dig iforget.info +nostats +nocomments +nocmd ; \u0026lt;\u0026lt;\u0026gt;\u0026gt; DiG 9.8.3-P1 \u0026lt;\u0026lt;\u0026gt;\u0026gt; iforget.info +nostats +nocomments +nocmd ;; global options: +cmd ;iforget.info. IN A iforget.info. 30 IN CNAME thisiswangle.github.io. thisiswangle.github.io. 1976 IN CNAME github.map.fastly.net. github.map.fastly.net. 30 IN A 103.245.222.133 fastly.net. 73096 IN NS ns3.p04.dynect.net. fastly.net. 73096 IN NS ns4.p04.dynect.net. fastly.net. 73096 IN NS ns2.p04.dynect.net. fastly.net. 73096 IN NS ns1.p04.dynect.net. ns1.p04.dynect.net. 74973 IN A 208.78.70.4 ns2.p04.dynect.net. 74756 IN A 204.13.250.4 ns3.p04.dynect.net. 74789 IN A 208.78.71.4 ns4.p04.dynect.net. 153 IN A 204.13.251.4 IP 变成了 103.245.222.133, 澳大利亚 IP ，进入亚洲组了。\n当同时设置了 iforget.info 和 www.iforget.info, 再添加一个 CNAME 文件到自己的博客根目录下。\n如果 CNAME 的内容是\niforget.info\n浏览器中输入 www.iforget.info 时，Github Pages 将会永久跳转(301)到 iforget.info, 反之亦然。\n如果你能看到本页，说明本站已经拿到了 GFW 认证。\n","permalink":"https://lewang.dev/posts/2014-05-06-github-pages-dns-settings/","summary":"这几天每次 Push 博客到 Github Pages 时总会收到一封邮件：\nThe page build completed successfully, but returned the following warning:\nGitHub Pages recently underwent some improvements (https://github.com/blog/1715-faster-more-awesome-github-pages) to make your site faster and more awesome, but we\u0026rsquo;ve noticed that iforget.info isn\u0026rsquo;t properly configured to take advantage of these new features. While your site will continue to work just fine, updating your domain\u0026rsquo;s configuration offers some additional speed and performance benefits. Instructions on updating your site\u0026rsquo;s IP address can be found at https://help.","title":"Github Pages 服务的域名设置"},{"content":"“悟性”是一个很虚的东西吗？\n前段时间和同学聊天，聊如何从众多的各方面初步看起来差不多应聘者中去做选择？聊来聊去最终落到了“悟性”上。我自己在突然想到悟性这个词的时候脑子几乎一片空白，不知道如何去判断一个人的悟性，或者说悟性到底是怎么样的，凭什么说一个人悟性好或者悟性差。\n昨天下午有个年龄不小的同学过来面试，聊了一个小时以后，向老板 汇报面试意见时，这同学各方面都很不错，但是总觉得有些地方不对，找不到词来形容自己内心当时的想法，于是结结巴巴的说，这种情况下，我要看一个人有没有悟性，接着我举了一个例子来说明什么时悟性：\n假如一个人，被安排了事情 A，我觉得有悟性的人，可能在做 A 事情的时候，把相关的事情 B、C 都做了，或者要求时做到A，但是主动把结果做成 A++\n说完后，我觉得不妥：这可能不是一个人有没有悟性的问题，而是这个人主动不主动、有没有热情的问题。\n到底什么怎样才能通过一些具体的例子体会悟性这个词的含义？\n佛家讲究悟性。《心经》仅有 260 个字，有悟性的人才可以写出《心经与生活智慧》，甚至心经和量子物理都有关系，你看了之后，才发现：哦，原来心经是说这些的啊。\n道家也讲究悟性。当年菩提老祖在孙猴子头上敲三下的时候，要是孙猴子没有悟性只有兽性的话，估计就没有后面的孙悟空了。\n自己之前团队中有个新同事，年龄比我长，团队之间大家相互做 Code Review，且不说代码质量， 从代码风格上我就对此人无语了：从不注意已有代码在变量、函数、文件命名上的一些基本的规范，别人不告诉他，就不会意识到这个问题。一些看得见的东西不一定非要教条式的写到文件里，然后去专门花时间去学,去强制。有些问题，还是自己意识到比较好，从别人口里说出来，那就不好玩了。\n一句话说得好：\n不怕狼一样的对手，就怕猪一样的队友。\n不多说了，不强求，悟性有可能就是天生的。做不了大师兄，做二师弟也不赖。\n","permalink":"https://lewang.dev/posts/2014-04-26-power-of-understanding/","summary":"“悟性”是一个很虚的东西吗？\n前段时间和同学聊天，聊如何从众多的各方面初步看起来差不多应聘者中去做选择？聊来聊去最终落到了“悟性”上。我自己在突然想到悟性这个词的时候脑子几乎一片空白，不知道如何去判断一个人的悟性，或者说悟性到底是怎么样的，凭什么说一个人悟性好或者悟性差。\n昨天下午有个年龄不小的同学过来面试，聊了一个小时以后，向老板 汇报面试意见时，这同学各方面都很不错，但是总觉得有些地方不对，找不到词来形容自己内心当时的想法，于是结结巴巴的说，这种情况下，我要看一个人有没有悟性，接着我举了一个例子来说明什么时悟性：\n假如一个人，被安排了事情 A，我觉得有悟性的人，可能在做 A 事情的时候，把相关的事情 B、C 都做了，或者要求时做到A，但是主动把结果做成 A++\n说完后，我觉得不妥：这可能不是一个人有没有悟性的问题，而是这个人主动不主动、有没有热情的问题。\n到底什么怎样才能通过一些具体的例子体会悟性这个词的含义？\n佛家讲究悟性。《心经》仅有 260 个字，有悟性的人才可以写出《心经与生活智慧》，甚至心经和量子物理都有关系，你看了之后，才发现：哦，原来心经是说这些的啊。\n道家也讲究悟性。当年菩提老祖在孙猴子头上敲三下的时候，要是孙猴子没有悟性只有兽性的话，估计就没有后面的孙悟空了。\n自己之前团队中有个新同事，年龄比我长，团队之间大家相互做 Code Review，且不说代码质量， 从代码风格上我就对此人无语了：从不注意已有代码在变量、函数、文件命名上的一些基本的规范，别人不告诉他，就不会意识到这个问题。一些看得见的东西不一定非要教条式的写到文件里，然后去专门花时间去学,去强制。有些问题，还是自己意识到比较好，从别人口里说出来，那就不好玩了。\n一句话说得好：\n不怕狼一样的对手，就怕猪一样的队友。\n不多说了，不强求，悟性有可能就是天生的。做不了大师兄，做二师弟也不赖。","title":"悟性"},{"content":" 成长就是看自己以前写的东西（包括微博、博客、说说等等）的时候觉得以前自己很 SB, 然后迫切地想把它们删掉\n这句话不止一次看到，反思自己，确实是那么回事儿，不能太赞同哦（我了个去上海腔了呢）。\n先前早些时候，把新浪博客和人人删的一干二净，也就是过把自己过去的所有文字的创造付之一炬，说没有一点惋惜，那是不可能的，想想当年自己顶着农村小孩黝黑皮肤初来乍到大城市，牛流不分，不怕喝不上牛奶只能喝豆浆的尴尬，给学校里各个杂志投稿，现在读来那些稿件，莫不觉得自己二逼。还好是些低年级学生玩的校办杂志，没多少人，为了回忆二逼的岁月，我特地还留了一本，没有其他人变态一样也留了一本吧。\n哦，对了，如果你想删掉所有的 SNS 平台的日志，不如学门编程的手艺，使用他们所谓的开放接口删了他们。不知道人人网的 3g.renren.com, 如果还能访问那可以试试小码哥几年前弄的人人自杀小工具 xiaomg.sinaapp.com，如果恰好能帮你删掉所有的东西，那么就是人人网的不对了，这么多年了，一点进步都没有，还有几个人在山寨机上开个ucweb访问你的3g网站。小工具的代码就不要看了，惨不忍闻，套用前面的话，写得太二逼了。说不定哪天就会被删掉，就像删这些日志一样。\n感性的文字都已经被删掉了，这下子没人看的得到我过去的无知，也只有我自己知道自己过去的无畏。\n准备把散落在各处的技术博文都挪到这里来，二逼 SB 可能不大适合形容一个人的手艺，但是菜鸟二字恰如其分。\n又一个菜鸟来了。大牛们见笑了。\n","permalink":"https://lewang.dev/posts/2014-04-25-grow-up/","summary":"成长就是看自己以前写的东西（包括微博、博客、说说等等）的时候觉得以前自己很 SB, 然后迫切地想把它们删掉\n这句话不止一次看到，反思自己，确实是那么回事儿，不能太赞同哦（我了个去上海腔了呢）。\n先前早些时候，把新浪博客和人人删的一干二净，也就是过把自己过去的所有文字的创造付之一炬，说没有一点惋惜，那是不可能的，想想当年自己顶着农村小孩黝黑皮肤初来乍到大城市，牛流不分，不怕喝不上牛奶只能喝豆浆的尴尬，给学校里各个杂志投稿，现在读来那些稿件，莫不觉得自己二逼。还好是些低年级学生玩的校办杂志，没多少人，为了回忆二逼的岁月，我特地还留了一本，没有其他人变态一样也留了一本吧。\n哦，对了，如果你想删掉所有的 SNS 平台的日志，不如学门编程的手艺，使用他们所谓的开放接口删了他们。不知道人人网的 3g.renren.com, 如果还能访问那可以试试小码哥几年前弄的人人自杀小工具 xiaomg.sinaapp.com，如果恰好能帮你删掉所有的东西，那么就是人人网的不对了，这么多年了，一点进步都没有，还有几个人在山寨机上开个ucweb访问你的3g网站。小工具的代码就不要看了，惨不忍闻，套用前面的话，写得太二逼了。说不定哪天就会被删掉，就像删这些日志一样。\n感性的文字都已经被删掉了，这下子没人看的得到我过去的无知，也只有我自己知道自己过去的无畏。\n准备把散落在各处的技术博文都挪到这里来，二逼 SB 可能不大适合形容一个人的手艺，但是菜鸟二字恰如其分。\n又一个菜鸟来了。大牛们见笑了。","title":"成长"},{"content":" 这张照片很多人应该都见过，名副其实的二十世纪最伟大科学家们的合影。爱因斯坦占据了最明显的位置，德布罗意也在其中。\n这里是摘自百度百科的一些内容：\n在光具有波粒二象性的启发下，法国物理学家德布罗意（1892～1987）在 1924 年提出一个假说，指出波粒二象性不只是光子才有，一切微观粒子，包括电子和质子、中子，都有波粒二象性。他把光子的动量与波长的关系式 p=h/λ 推广到一切微观粒子上，指出：具有质量 m 和速度 v 的运动粒子也具有波动性，这种波的波长等于普朗克恒量 h 跟粒子动量 mv 的比，即 λ=h/(mv)。这个关系式后来就叫做德布罗意公式。\n从德布罗意公式很容易算出运动粒子的波长。\n例如，电子的电荷是 1.6×10^-19 库，质量是 0.91×10^-30 千克，经过 200 伏电势差加速的电子获得的能量 E=Ue=200×1.6×10-19 焦 =3.2×10-17 焦。这个能量就是电子的动能，即0.5mv^2=3.2×10^-17 焦，因此 v=8.3910^6 米/秒。于是，按照德布罗意公式这运动电子的波长是 λ=h/(mv)=6.6310^-34/(9.110^-318.39*10^6)=8.7×10-11 米，或者 0.87 埃。\n我们看到，这个波长与伦琴射线的波长相仿。前面讲过，这样短的波长，只有用晶体做衍射光栅才能观察到衍射现象。后来人们的确用这种办法观察到了电子的衍射，从而证明了德布罗意假说的正确性。\n是不是有点印象了，这就是高中物理课本里面的内容吧。\n前几天和公司几个童鞋一起吃饭，先是有人说《水知道答案》，且先不说它是不是伪科普，我们试图用一些我们想得到的知识来说明一些问题，于是我想到了物质波，也就是德布罗意波。让我惊愕的是，那三位同学一致表示他们完全不知道什么是德布罗意波，搞得我仿佛穿越了，穿越到阿西莫夫的基地里去了。\n一定是在耍我。\n如果德布罗意波都不知道，那肯定也错过了德布罗意本人和他那篇博士论文的八卦故事了。不多说了，还是偷着乐吧。\n哈哈\n","permalink":"https://lewang.dev/posts/2014-03-13-who-is-de-broglie/","summary":"这张照片很多人应该都见过，名副其实的二十世纪最伟大科学家们的合影。爱因斯坦占据了最明显的位置，德布罗意也在其中。\n这里是摘自百度百科的一些内容：\n在光具有波粒二象性的启发下，法国物理学家德布罗意（1892～1987）在 1924 年提出一个假说，指出波粒二象性不只是光子才有，一切微观粒子，包括电子和质子、中子，都有波粒二象性。他把光子的动量与波长的关系式 p=h/λ 推广到一切微观粒子上，指出：具有质量 m 和速度 v 的运动粒子也具有波动性，这种波的波长等于普朗克恒量 h 跟粒子动量 mv 的比，即 λ=h/(mv)。这个关系式后来就叫做德布罗意公式。\n从德布罗意公式很容易算出运动粒子的波长。\n例如，电子的电荷是 1.6×10^-19 库，质量是 0.91×10^-30 千克，经过 200 伏电势差加速的电子获得的能量 E=Ue=200×1.6×10-19 焦 =3.2×10-17 焦。这个能量就是电子的动能，即0.5mv^2=3.2×10^-17 焦，因此 v=8.3910^6 米/秒。于是，按照德布罗意公式这运动电子的波长是 λ=h/(mv)=6.6310^-34/(9.110^-318.39*10^6)=8.7×10-11 米，或者 0.87 埃。\n我们看到，这个波长与伦琴射线的波长相仿。前面讲过，这样短的波长，只有用晶体做衍射光栅才能观察到衍射现象。后来人们的确用这种办法观察到了电子的衍射，从而证明了德布罗意假说的正确性。\n是不是有点印象了，这就是高中物理课本里面的内容吧。\n前几天和公司几个童鞋一起吃饭，先是有人说《水知道答案》，且先不说它是不是伪科普，我们试图用一些我们想得到的知识来说明一些问题，于是我想到了物质波，也就是德布罗意波。让我惊愕的是，那三位同学一致表示他们完全不知道什么是德布罗意波，搞得我仿佛穿越了，穿越到阿西莫夫的基地里去了。\n一定是在耍我。\n如果德布罗意波都不知道，那肯定也错过了德布罗意本人和他那篇博士论文的八卦故事了。不多说了，还是偷着乐吧。\n哈哈","title":"谁是德布罗意"},{"content":"为了自己做好网站的架构，找了知乎，豆瓣，大众点评和百姓网来做参考。这里主要来看域名和 CDN 相关的部分，后台的架构也还是要边学便实践。\n大概情况 从前端分析了知乎的域名和 DNS 情况，同时对比了一下豆瓣、大众点评和百姓网。从域名、DNS、CDN 等使用角度来说，他们之间大同小异。\n使用的分析工具 Chrome WHOIS DNSLookup 非 CDN 使用 记录 域名 用途 路径 A zhihu.com 使用 301 跳转到 www.zhuhu.com IP A zhi.hu 使用 302 跳转到 www.zhihu.com IP A www.zhihu.com IP A comet.zhihu.com WebSocket 推送消息 IP A analytics.zhihu.com 知乎自己的统计分析，同时使用了 Google Analytics IP 大部分都是用了 301 或者 302 跳转，注意他们的区别：301 是永久跳转(浏览器会做客户端端缓存)，而 302 是临时跳转。此外 baidu.com 到 www.baidu.com 并不是使用 301 或者 302 ，而是使用下面的一段 HTML 代码\n\u0026lt;html\u0026gt; \u0026lt;meta http-equiv=\u0026#34;refresh\u0026#34; content=\u0026#34;0;url=http://www.baidu.com/\u0026#34; /\u0026gt; \u0026lt;/html\u0026gt; 相比而言，我觉得使用 301 更 Geek，更好一些。\nCDN 使用 记录 域名 用途 路径 CNAME static.analytics.zhihu.com CDN, 看 CNAME 以为是七牛 CDN，其实用了 ChinaNetCenter 的 CDN d.qiniudn.com \u0026gt; wsall.36tr.com.wscdns.com \u0026gt; 08911.xdwscache.glb0.lxdns.com \u0026gt; IP CNAME static.zhihu.com 网页资源文件 cdn，包括 css，js，image，不包括用户头像、用户发布的图片等 同上 CNAME p1.zhimg.com 用户头像，用户发布的图片, 这里使用又拍云 CDN zhcdn-img.b0.aicdn.com \u0026gt; ctn.b9.aicdn.com \u0026gt; IP CNAME p2.zhimg.com 用户头像，用户发布的图片，这里使用了七牛 CDN zhimg0.qiniudn.com \u0026gt; 其它非七牛 CDN CNAME p3.zhimg.com 与 p1.zhimg.com 相同 CNAME p4.zhimg.com 与 p1.zhimg.com 相同 CNAME s1.zhimg.com 视频片段等 这里使用了七牛 CDN zhimg0.qiniudn.com \u0026gt; 其它非七牛 CDN CNAME s2.zhimg.com 这里使用了七牛 CDN zhimg0.qiniudn.com \u0026gt; 其它非七牛 CDN CNAME s3.zhimg.com 这里使用了七牛 CDN zhimg0.qiniudn.com \u0026gt; 其它非七牛 CDN CNAME s4.zhimg.com 这里使用了七牛 CDN zhimg0.qiniudn.com \u0026gt; 其它非七牛 CDN p1-p4， s1-s4 由知乎服务器端按照一定算法选择（有可能就是等概率随机）\n不得不有点疑问？为什么七牛 CDN 最后都直到别人的 CDN 服务器上去了，而又拍云全部是 A 记录到自己的服务器。又拍强调的自建机房就是这个意思？\n静态资源版本控制 方法 知乎 在文件名前添加类似 md5 的字符串，如 app.js, 发布版本为 f853b06af5428ff5f78f66e3d09397e2.app.js 百姓网 如 gallery.js 发布版本为 gallery.f07b79d6.js 豆瓣 将类似 md5 值放在路径中，如http://img3.douban.com/f/shire/55c9fe0e9ecb5725037e9839fc515504008dae74/js/ad.js 大众点评 和百姓网类似，如发布版本为 ga2.min.acd5dfe89e87135b4eef62dcf81ef849.js 由于 CDN 使用简单镜像＋缓存的功能，每一个缓存的文件有一定的时效性，而且基本都是静态文件，不支持 Query String （如 ?ver=201413），于是需要在源头控制将要被分发到 CDN 中文件的版本。最为简单的做法就是对开发人员透明，在发布项目的时候，通过计算文件的 hash 签名或者 md5 签名来做类 HTML 等文本代码的重构。\nDNS 选择 域名 DNS 服务商 zhihu.com DNSPOD 企业版 zhimg.com DNSPOD 免费版 baixing.com DNSPOD 企业版 baixing.net DNSPOD 企业版 dianping.com 自建 douban.com 自建 ","permalink":"https://lewang.dev/posts/2014-03-06-arch-of-website-domain-cdn/","summary":"为了自己做好网站的架构，找了知乎，豆瓣，大众点评和百姓网来做参考。这里主要来看域名和 CDN 相关的部分，后台的架构也还是要边学便实践。\n大概情况 从前端分析了知乎的域名和 DNS 情况，同时对比了一下豆瓣、大众点评和百姓网。从域名、DNS、CDN 等使用角度来说，他们之间大同小异。\n使用的分析工具 Chrome WHOIS DNSLookup 非 CDN 使用 记录 域名 用途 路径 A zhihu.com 使用 301 跳转到 www.zhuhu.com IP A zhi.hu 使用 302 跳转到 www.zhihu.com IP A www.zhihu.com IP A comet.zhihu.com WebSocket 推送消息 IP A analytics.zhihu.com 知乎自己的统计分析，同时使用了 Google Analytics IP 大部分都是用了 301 或者 302 跳转，注意他们的区别：301 是永久跳转(浏览器会做客户端端缓存)，而 302 是临时跳转。此外 baidu.com 到 www.baidu.com 并不是使用 301 或者 302 ，而是使用下面的一段 HTML 代码\n\u0026lt;html\u0026gt; \u0026lt;meta http-equiv=\u0026#34;refresh\u0026#34; content=\u0026#34;0;url=http://www.baidu.com/\u0026#34; /\u0026gt; \u0026lt;/html\u0026gt; 相比而言，我觉得使用 301 更 Geek，更好一些。","title":"从域名和 CDN 来看网站架构"},{"content":"Vagrant 是一个构建虚拟开发环境的利器，它使得在团队中很容易共享开发环境，有了它，不必每个人都搭建一个自己的开发环境了。在没有使用 vagrant 之前，我在 windows 中安装了 virtualbox，然后在 virtualbox 中安装了一个 ubuntu server。在 ubuntu 中安装了 mysql、jdk 等软件，并且设置了一个共享目录，以便于将工程代码直接共享给 ubuntu。在外部开发调试过程中（Java 项目），我需要在 virtualbox 中设置好几个端口映射，如 mysql，ssh 等等。这样在 cygwin 里面就可以通过 ssh 登录 ubuntu server 了，为了使得 ssh 登录不用每次设置密码，还需要手动生成公钥私钥。换机器了，这一切我又得重新配置。新同事来了，他也得像我这样重新配置一遍。有可能他还会有问题说：为什么按照你的说明，代码在我这里却无法运行呢？\n一切都从 Yining 告诉了我 vagrant 之后发生了变化，原来一切都这么简单。\n安装 VirtualBox Vagrant 其实是对 virtualbox 做了一层包装，它让使用 virtualbox 作为虚拟机更为简单。当然也可以使用 vmware，使用 vmware 那得找一个 license 了。\n好吧，去这里 下载 VirtualBox。我使用的是 4.3.8 for OSX\n安装 Vagrant 下载 vagrant\n添加虚拟机镜像到 Vagrant vagrant box add vdevenv ~/Workspace/vagrant/boxes/vdevenv-0.0.0.1.box vagrant 的干净的镜像可以在这里下载:\nUbuntu precise 32 VirtualBox Ubuntu precise 64 VirtualBox 更多镜像 在工作目录启动 Vagrant 进入自己的代码所在的目录，启动刚刚添加的虚拟机镜像\nvagrant init vdevenv vagrant up vagrant ssh # 登录到虚拟机 进入 /vagrant 目录，这里就是共享的目录，所有的代码都在这里。\nVagrant 设置 修改 Vagrantfile\n分享开发环境 vagrant package 执行之后在工作目录下面会生成一个*.bax 的文件，把这个文件拷给别人就可以了\n常用 Vagrant 命令 vagrant ssh vagrant status vagrant halt vagrant up ","permalink":"https://lewang.dev/posts/2014-03-03-building-devenv-by-vagrant/","summary":"Vagrant 是一个构建虚拟开发环境的利器，它使得在团队中很容易共享开发环境，有了它，不必每个人都搭建一个自己的开发环境了。在没有使用 vagrant 之前，我在 windows 中安装了 virtualbox，然后在 virtualbox 中安装了一个 ubuntu server。在 ubuntu 中安装了 mysql、jdk 等软件，并且设置了一个共享目录，以便于将工程代码直接共享给 ubuntu。在外部开发调试过程中（Java 项目），我需要在 virtualbox 中设置好几个端口映射，如 mysql，ssh 等等。这样在 cygwin 里面就可以通过 ssh 登录 ubuntu server 了，为了使得 ssh 登录不用每次设置密码，还需要手动生成公钥私钥。换机器了，这一切我又得重新配置。新同事来了，他也得像我这样重新配置一遍。有可能他还会有问题说：为什么按照你的说明，代码在我这里却无法运行呢？\n一切都从 Yining 告诉了我 vagrant 之后发生了变化，原来一切都这么简单。\n安装 VirtualBox Vagrant 其实是对 virtualbox 做了一层包装，它让使用 virtualbox 作为虚拟机更为简单。当然也可以使用 vmware，使用 vmware 那得找一个 license 了。\n好吧，去这里 下载 VirtualBox。我使用的是 4.3.8 for OSX\n安装 Vagrant 下载 vagrant\n添加虚拟机镜像到 Vagrant vagrant box add vdevenv ~/Workspace/vagrant/boxes/vdevenv-0.0.0.1.box vagrant 的干净的镜像可以在这里下载:\nUbuntu precise 32 VirtualBox Ubuntu precise 64 VirtualBox 更多镜像 在工作目录启动 Vagrant 进入自己的代码所在的目录，启动刚刚添加的虚拟机镜像","title":"使用 Vagrant 构建开发环境"},{"content":"前几天入手了一台 Macbook Pro Retina，把 BIG 提到了很爽的地步。经过几天的折腾，可以和 windows，ubuntu desktop 说 Byebye 了。之前整整用了一年的 Ubuntu 来工作，我用它来写 C、Java 和 Python, 要是 windows 没有 cygwin, 没有 MacType，那该多么恐怖。后来用了一段时间的 MBP，让我坚定了以后要自己买一个 MBP。\nubuntu 那么的惹人爱，因为它有个还挺好用的 GUI，从 debian 那继承过来的 apt-get, 一下子把一个从 redhat 9 用到 fedora 4 的穷学生拉到了 ubuntu 的阵营，至此，deskop 就被 ubuntu 占领了。在此之前，我还用段时间的 debian 和 gentoo。\n不得不说，debian 太伟大太牛逼了，有一次要在一个 MIPS 的小设备上编译安装 erlang， openssl，libcurl 等做一些实验, 要跨平台编译这些软件安装到小设备上去，费了半天终于把这些都编好了，还写了一个编译脚步，结果 erlang 运行的时候虚拟机总是报错，也不知道怎么回事。后来注意到 debian 居然有 MIPS，ARM 的源，后来在 MIPS 设备上安装软件做实验，我再也没有遇到问题，一切都用 apt-get 搞定了。\n除了 apt-get, 我想最令我惊喜还有 ubuntu 自带了 python 2.7，让我很容易在两台电脑之间拷贝文件：\npython -m SimpleHTTPServer 是不是很爽，rMBP 也带了 Python。不过传文件也不用 python 了，有更好用 Airdrop 可以用。\nrMBP 刚到手的时候，我创建了第一个目录是 Workspace，小四同学问我为啥第一个字母要大写，输入多不方便，我说我 home 目录下都是大写的，这样我 ls 会比较漂亮。\n其实我不想把我的系统变得很乱，我喜欢保持某些一致，让自己的心里觉得有点舒服。就像上学的时候，看到黑板上有点粉笔的字迹没有擦干净，就想伸手过去摸一下把它擦掉。\n我想，nodejs，ruby，python 的粉丝里不少都是这样的吧，不然怎么会有 nvm，rvm，virtualenv。\n如果你也在用 python，而且还经常在 github 上面拉别人的代码过来玩，或者安装一些 python lib, 我推荐你用 virtualenv:\nsudo easy_install pip sudo pip install virtualenv 接下来就可以在虚拟的环境里面随便折腾了，而且你的系统还会保持干干净净。\nvirtualenv venv 每次使用 venv 这个虚拟环境时都要记得激活它，所以可以 alias 到 .bash_profile 里面去。\n. venv/bin/activate 上面那个点(dot/period) 是 source 的代名词, 相当于\nsource venv/bin/activate 接下来通过 pip 安装的东西都会在这个虚拟环境中了，是不是很好玩。\nLove my rMBP, Love my python.\n","permalink":"https://lewang.dev/posts/2014-03-02-python-on-my-rmbp/","summary":"前几天入手了一台 Macbook Pro Retina，把 BIG 提到了很爽的地步。经过几天的折腾，可以和 windows，ubuntu desktop 说 Byebye 了。之前整整用了一年的 Ubuntu 来工作，我用它来写 C、Java 和 Python, 要是 windows 没有 cygwin, 没有 MacType，那该多么恐怖。后来用了一段时间的 MBP，让我坚定了以后要自己买一个 MBP。\nubuntu 那么的惹人爱，因为它有个还挺好用的 GUI，从 debian 那继承过来的 apt-get, 一下子把一个从 redhat 9 用到 fedora 4 的穷学生拉到了 ubuntu 的阵营，至此，deskop 就被 ubuntu 占领了。在此之前，我还用段时间的 debian 和 gentoo。\n不得不说，debian 太伟大太牛逼了，有一次要在一个 MIPS 的小设备上编译安装 erlang， openssl，libcurl 等做一些实验, 要跨平台编译这些软件安装到小设备上去，费了半天终于把这些都编好了，还写了一个编译脚步，结果 erlang 运行的时候虚拟机总是报错，也不知道怎么回事。后来注意到 debian 居然有 MIPS，ARM 的源，后来在 MIPS 设备上安装软件做实验，我再也没有遇到问题，一切都用 apt-get 搞定了。\n除了 apt-get, 我想最令我惊喜还有 ubuntu 自带了 python 2.7，让我很容易在两台电脑之间拷贝文件：","title":"Python on My rMBP"},{"content":"总体说明 我使用联通 10M 光网，SSH 在多个时间段登录两个主机都是非常的快，但是阿里云的 SSH 有时会断开连接，需要重新登录(一天遇到 n 次)，ucloud 一次都没有断开连接过。两者总体操作上都很流畅。平时我比较习惯于使用 ubuntu，于是在两个主机上都安装了 Ubuntu 12.04_64 的 Server 版本。在安装初始化系统过程中(测试过两次)，Ucloud 的速度要快于阿里云主机，粗略估计都是在 20s 以内，因此差别不大。\n阿里云主机安装过程中不需要人为参与，选好主机配置即可，主机一旦创建完成，用户名和口令会发送到测试者手机上，而且直接发送的是 root 账户口令。Ucloud 除了选择配置之外，还需要设置强登录密码，使用普通用户登录，感觉更 geek 一点。\n默认情况配置下，关闭重启服务器，Ucloud 会发送主机变化短信到手机，阿里云不会。阿里云在系统负载预警方面比较全面，ucloud 侧重于 server 上在线状态和相关服务的预警。\n在试用和测试过程中我主要偏向于静态的测试，后面还有一个好玩的 Super PI 测试。\n阿里云和 ucloud 都是主机，和实际的服务器使用上没有什么区别。另外阿里云有 paas，需要单独购买，目前支持 php 和 nodejs，一些基本的服务如 memcached 等都可直接使用。\n如果做互备，如果我们不使用这两个云平台各自特有的一些服务(主要是阿里云有一些如开放存储服务 OSS 等)，相互切换难度上应该是差不多的。\n主机概览 项目 阿里云 ucloud 按量付费 支持(不能升级或者调整主机配置*) 不支持 包年包月 支持 支持 操作系统 可重置 可重装 CPU 升级 支持(需要关机) 支持(需要关机) 内存升级 支持(需要关机) 支持(需要关机) 磁盘扩容 支持，可以升级或者添加新的磁盘，最大为 2000G 支持，可以扩容最大到 1000G（与系统盘在同一个主机上），\u0026lt;/br/\u0026gt;或者添加新的网络磁盘 udisk 磁盘镜像 支持 支持 带宽升级 支持 支持 其它服务[需要单独购买] 项目 阿里云 ucloud 负载均衡服务 支持(目前免费)，需要两台主机以上 支持(目前免费)，需要两台主机以上 关系型数据库服务 支持，mysql 和 sqlserver 支持，mysql 和 percona 特有的服务 开放存储服务 OSS、开放数据处理服务 ODPS、开放结构化数据服务 OTS CDN、短信服务（可二次开发）、与又拍、DNSPod 和搜狐 SendCloud 直接绑定 *按量付费不支持更换操作系统；不支持配置变更功能（包括带宽升级、CPU 和内存升级、新增数据盘）;“包年包月“和”按量付费“不支持相互更换;1 台云服务器只能选择 1 种，无法同时选择\n收费结构(参考) 项目 阿里云 ucloud 价格动态计算 http://buy.aliyun.com/ http://www.ucloud.cn/price 1 核 CPU/2GB 内存/100G 数据盘/10M 带宽 8,620 元/年 5,200 元/年 2 核 CPU/4GB 内存/100G 数据盘/10M 带宽 10,590 元/年 6,900 元/年 4 核 CPU/16GB 内存/1000G 数据盘/10M 带宽 24,030 元/年 19,800 元/年 4 核 CPU/16GB 内存/1000G 数据盘/20M 带宽 3,4030 元/年 24,900 元/年 2 核 CPU/4GB 内存/100G 数据盘/20M 带宽 20,590 元/年 11,400 元/年 4 核 CPU/8GB 内存/100G 数据盘/20M 带宽 24,530 元/年 14,800 元/年 …… …… …… 试用情况 项目 阿里云 ucloud 主机名 aliyun-s1 ucloud-s1 用户名 root ubuntu[默认普通用户权限] 登录口令 自动生成默认密码，弱密码 必须设置强密码 数据中心 华东青岛 BGP 北京 BGP(2M)，客服推荐使用北京 BGP，另外还有华东双线可选 CPU 2 核 Intel(R) Xeon(R) CPU E5-2420 0 @ 1.90GHz 2 核 QEMU Virtual CPU version (cpu64-rhel6) 内存 1.5GB 2GB 系统盘[ 免费] 20GB 20GB OS Ubuntu 12.04 64 位[只有 64 位]，支持 Centos(5.7,5.8,6.3)、Debian 6.06、Ubuntu 12.04, RHEL(5.4,5.7)、Windows(2003,2008r2)等 64 位版本 Ubuntu 12.04 64 位，比阿里云支持更多的 OS，并且都有 32 位版本可选 uptime 10:44:11 up 54 min, 1 user, load average: 0.00, 0.01, 0.04 10:44:11 up 54 min, 1 user, load average: 0.00, 0.01, 0.04 监控 默认没有发送短信 默认情况下会发送主机状态变化到注册者手机 ##监控和预警\n###站点可用性监控\n项目 阿里云 ucloud ping 支持 支持 端口或协议 支持，傻瓜式，预制好了一些常用的，如 HTTP 监控、TCP 端口监控、UDP 监控、DNS 监控、POP3 监控、SMTP 监控、FTP 监控 需要自己定义端口和监控策略 预警 短信或者邮件 短信或者邮件 ###服务器监控\n项目 阿里云 ucloud 内容 CPU 利用率监控、内存利用率监控、磁盘利用率监控、网络流量监控、进程状态监控、进程数量监控、进程 CPU 资源监控、进程内存资源监控、TCP 连接数监控、Swap 利用率监控、CPU 负载(load)监控、Filesystem 可用性监控、服务监控、日志监控 无，有数据视图，需要登录查看系统负载状态 ping 测试 测试工具 http://ping.chinaz.com/ 测试结果 阿里云(IP:115.28.3.138)\n线路 最快节点 响应 最慢节点 响应 平均响应 所有线路 上海[电信] 13 毫秒 德国[海外] 311 毫秒 73 毫秒 电信 上海[电信] 13 毫秒 香港[电信] 187 毫秒 62 毫秒 多线 上海[多线] 22 毫秒 北京[多线] 39 毫秒 30 毫秒 联通 北京[联通] 16 毫秒 泉州[联通] 65 毫秒 38 毫秒 移动 上海[移动] 22 毫秒 河北[移动] 68 毫秒 38 毫秒 海外 香港[海外] 46 毫秒 德国[海外] 311 毫秒 165 毫秒 ucloud(IP:42.62.56.129)\n线路 最快节点 响应 最慢节点 响应 平均响应 所有线路 北京[联通] 3 毫秒 德国[海外] 403 毫秒 64 毫秒 电信 陕西西安[电信] 21 毫秒 四川绵阳[电信] 53 毫秒 35 毫秒 多线 北京[多线] 4 毫秒 河南郑州[多线] 53 毫秒 28 毫秒 联通 北京[联通] 3 毫秒 深圳[联通] 73 毫秒 33 毫秒 移动 河北[移动] 26 毫秒 福建厦门[移动] 45 毫秒 35 毫秒 海外 韩国[海外] 49 毫秒 德国[海外] 403 毫秒 186 毫秒 经过多次 ping 测试，ucloud 要比阿里云快一些。这里使用的客服推荐的各自的 BGP 网络：阿里云华东青岛机房和 ucloud 北京 BGP 机房。\n下载测试 在各自主机上使用 wget 下载http://mirrors.163.com/ubuntu-releases/13.04/ubuntu-13.04-server-i386.iso\n项目 阿里云 ucloud 解析后 IP 123.58.173.106 123.58.173.106 平均速度 913K/s 5.41M/s ucloud 下载速度达到了 5.41M/s，可能它和网易的镜像比较近吧。好吧，那来测试一下 scp 吧。 在各自服务器上都 dd 一个 100M 文件出来，然后分别使用 scp 上传和下载。之后搭建一个 Niginx Server 来测试一下 HTTP。 新建的 20G 数据盘情况(tune2fs -l /dev/vdb |grep Block)\n项目 阿里云 ucloud Block Size 4096 4096 使用 dd 创建 2000M 文件，block 大小为 8K\ndd if=/dev/zero of=file_8k bs=8k count=250k 使用 dd 创建 2000M 文件，block 大小为 4K\ndd if=/dev/zero of=file_4k bs=4k count=500k 项目 阿里云 ucloud file_8k 79.3401 s, 26.4 MB/s 6.84755 s, 306 MB/s file_4k 77.6012 s, 27.0 MB/s 6.36673 s, 329 MB/s ucloud 数据盘是和系统盘在一起的，应该不是那种分布式存储，它的 udisk 估计和阿里云的数据盘比较类似，udisk 没有测。 使用 scp 主动拷贝文件\n项目 阿里云-\u0026gt;ucloud ucloud -\u0026gt;阿里云 file_8k 277.7KB/s 234.5KB/s file_4k 288.8KB/s 242.4KB/s 阿里云 ab 测试(n=1000, 4.1k 大小 html 文件)\n并发数 reqs/s 平均等待时间 平均处理时间 1 3751.57 0.267 ms 0.267 ms 10 8809.25 1.135 ms 0.114 ms 20 9596.84 2.084 ms 0.104 ms 50 9851.63 5.075 ms 0.102 ms 100 9382.89 10.658 ms 0.107 ms 150 8800.88 17.044 ms 0.114 ms 200 6666.13 30.002 ms 0.150 ms 500 3740.30 133.679 ms 0.267 ms ucloud ab 测试(n=1000, 4.1k 大小 html 文件)\n并发数 reqs/s 平均等待时间 平均处理时间 1 6135.53 0.163 ms 0.163 ms 10 10202.83 0.980 ms 0.098 ms 20 15624.27 1.280 ms 0.064 ms 50 16508.19 3.029 ms 0.061 ms 100 14707.14 6.799 ms 0.068 ms 150 14320.49 10.475 ms 0.070 ms 200 10312.57 19.394 ms 0.097 ms 500 3805.55 131.387 ms 0.263 ms UnixBench(分数越高越好) 项目 阿里云 ucloud running 1 copy of tests 731.3 1150.5 running 2 parallel copies of tests 1401.4 2310.9 分值上两个主机的差别还是很明显，从报告的细节上来看，磁盘 IO、CPU 等方面差别也很明显。\n","permalink":"https://lewang.dev/posts/2013-08-09-benchmark-of-aliyun-and-ucloud/","summary":"总体说明 我使用联通 10M 光网，SSH 在多个时间段登录两个主机都是非常的快，但是阿里云的 SSH 有时会断开连接，需要重新登录(一天遇到 n 次)，ucloud 一次都没有断开连接过。两者总体操作上都很流畅。平时我比较习惯于使用 ubuntu，于是在两个主机上都安装了 Ubuntu 12.04_64 的 Server 版本。在安装初始化系统过程中(测试过两次)，Ucloud 的速度要快于阿里云主机，粗略估计都是在 20s 以内，因此差别不大。\n阿里云主机安装过程中不需要人为参与，选好主机配置即可，主机一旦创建完成，用户名和口令会发送到测试者手机上，而且直接发送的是 root 账户口令。Ucloud 除了选择配置之外，还需要设置强登录密码，使用普通用户登录，感觉更 geek 一点。\n默认情况配置下，关闭重启服务器，Ucloud 会发送主机变化短信到手机，阿里云不会。阿里云在系统负载预警方面比较全面，ucloud 侧重于 server 上在线状态和相关服务的预警。\n在试用和测试过程中我主要偏向于静态的测试，后面还有一个好玩的 Super PI 测试。\n阿里云和 ucloud 都是主机，和实际的服务器使用上没有什么区别。另外阿里云有 paas，需要单独购买，目前支持 php 和 nodejs，一些基本的服务如 memcached 等都可直接使用。\n如果做互备，如果我们不使用这两个云平台各自特有的一些服务(主要是阿里云有一些如开放存储服务 OSS 等)，相互切换难度上应该是差不多的。\n主机概览 项目 阿里云 ucloud 按量付费 支持(不能升级或者调整主机配置*) 不支持 包年包月 支持 支持 操作系统 可重置 可重装 CPU 升级 支持(需要关机) 支持(需要关机) 内存升级 支持(需要关机) 支持(需要关机) 磁盘扩容 支持，可以升级或者添加新的磁盘，最大为 2000G 支持，可以扩容最大到 1000G（与系统盘在同一个主机上），\u0026lt;/br/\u0026gt;或者添加新的网络磁盘 udisk 磁盘镜像 支持 支持 带宽升级 支持 支持 其它服务[需要单独购买] 项目 阿里云 ucloud 负载均衡服务 支持(目前免费)，需要两台主机以上 支持(目前免费)，需要两台主机以上 关系型数据库服务 支持，mysql 和 sqlserver 支持，mysql 和 percona 特有的服务 开放存储服务 OSS、开放数据处理服务 ODPS、开放结构化数据服务 OTS CDN、短信服务（可二次开发）、与又拍、DNSPod 和搜狐 SendCloud 直接绑定 *按量付费不支持更换操作系统；不支持配置变更功能（包括带宽升级、CPU 和内存升级、新增数据盘）;“包年包月“和”按量付费“不支持相互更换;1 台云服务器只能选择 1 种，无法同时选择","title":"阿里云和 ucloud 云主机评测"},{"content":"相关的环境变量 LD_LIBRARY_PATH ld-linux.so 寻找 shared object 的路径，优先加载出现在路径前面的 shared object。如，export LD_LIBRARY_PATH=/home/user/lib:$LD_LIBRARY_PATH LD_PRELOAD 指定优先供 ld-linux.so 加载的 shared object。如，export LD_PRELAOD=/home/user/lib/glibc.so，可以使用这个变量来改变加载顺序，例如我们自定义的 glibc.so 中实现了新的 strcmp 之类的函数，那么可以使用这个变量来实现函数的替换，实现注入 LD_DEBUG 使用这个环境变量来 debug 载入 shared object 的情况。如，export LD_DEBUG=files,这样会打印所有所有加载 shared object 的记录 一些工具 如何查看一个程序或者 shared object 加载哪些 shared object ldd，可以查看程序或者.so，如，\n$ ldd ./foo_test $ ldd ./libfoo.so 对于已经运行的程序，可以这样 $ cat /proc/PID/maps | awk \u0026#39;{print $6}\u0026#39;| grep \u0026#39;\\.so\u0026#39;| sort | uniq $ lsof -p PID | awk \u0026#39;{print $9}\u0026#39; | grep \u0026#39;\\.so\u0026#39; 没有启动的程序可以这样 $ strace ./foo_test 2\u0026gt;\u0026amp;1 | grep \u0026#39;^open(\u0026#34;.*\\.so\u0026#39; 也可以用上面提到的环境变量来看 shared object 的加载顺序\n$ export LD_DEBUG=files $ ./foo_test 顺便介绍下 lsof 查看占用端口的进程\nle@SH:~$ sudo lsof -i TCP:80 COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME nginx 1309 root 7u IPv4 8740 0t0 TCP *:http (LISTEN) nginx 1310 www-data 7u IPv4 8740 0t0 TCP *:http (LISTEN) nginx 1311 www-data 7u IPv4 8740 0t0 TCP *:http (LISTEN) nginx 1312 www-data 7u IPv4 8740 0t0 TCP *:http (LISTEN) nginx 1313 www-data 7u IPv4 8740 0t0 TCP *:http (LISTEN) 具体的请man lsof\nobjdump 和 readelf objdump 查看 object 的信息 $ objdump -t libfoo.so $ objdump -s -j .rodata -t libfoo.so readelf 读 elf 的信息 readelf -r libfoo.so ","permalink":"https://lewang.dev/posts/2013-03-04-so-load-path-in-linux/","summary":"相关的环境变量 LD_LIBRARY_PATH ld-linux.so 寻找 shared object 的路径，优先加载出现在路径前面的 shared object。如，export LD_LIBRARY_PATH=/home/user/lib:$LD_LIBRARY_PATH LD_PRELOAD 指定优先供 ld-linux.so 加载的 shared object。如，export LD_PRELAOD=/home/user/lib/glibc.so，可以使用这个变量来改变加载顺序，例如我们自定义的 glibc.so 中实现了新的 strcmp 之类的函数，那么可以使用这个变量来实现函数的替换，实现注入 LD_DEBUG 使用这个环境变量来 debug 载入 shared object 的情况。如，export LD_DEBUG=files,这样会打印所有所有加载 shared object 的记录 一些工具 如何查看一个程序或者 shared object 加载哪些 shared object ldd，可以查看程序或者.so，如，\n$ ldd ./foo_test $ ldd ./libfoo.so 对于已经运行的程序，可以这样 $ cat /proc/PID/maps | awk \u0026#39;{print $6}\u0026#39;| grep \u0026#39;\\.so\u0026#39;| sort | uniq $ lsof -p PID | awk \u0026#39;{print $9}\u0026#39; | grep \u0026#39;\\.so\u0026#39; 没有启动的程序可以这样 $ strace .","title":"Linux 共享库（动态链接库）相关的一些记录"},{"content":"以前做的openlayers的项目基本都是很水的，但是毕竟都结了，算是有一丁点的积累。不过做项目的时候也是边学便用，代码写得很糟糕，一直没想着留着。于是毕业了，换城市了，换了几次电脑，这些代码终于都没了（有些网友那里应该还有，我发送过几次给别人）。\n鉴于有不少openlayers的爱好者跟我讨论openlayers的一些开发问题，我想我还是把以前的思路整理一下，对openlayers做些精简和开发，弄一个直接可以使用的简单库出来。\n","permalink":"https://lewang.dev/posts/2012-11-13-about_openlayers/","summary":"以前做的openlayers的项目基本都是很水的，但是毕竟都结了，算是有一丁点的积累。不过做项目的时候也是边学便用，代码写得很糟糕，一直没想着留着。于是毕业了，换城市了，换了几次电脑，这些代码终于都没了（有些网友那里应该还有，我发送过几次给别人）。\n鉴于有不少openlayers的爱好者跟我讨论openlayers的一些开发问题，我想我还是把以前的思路整理一下，对openlayers做些精简和开发，弄一个直接可以使用的简单库出来。","title":"关于openlayers的一些说明"},{"content":"最近做一些Kernel方面的工作，从一头雾水开始，多做些记录吧。\naddr2line也可以根据指令地址定位C代码对应的行，但是对于Kernel module却不是很方便，使用gdb就要容易得多。\nNOTE：在使用gdb定位C代码之前需要开启-g选项编译内核或者module\n例如有这样的Call Trace\nCall Trace:\n[\u0026lt;8033265c\u0026gt;] dump_stack+0x8/0x30\n[\u0026lt;8003abbc\u0026gt;] warn_slowpath_common+0x70/0x98\n[\u0026lt;80041f10\u0026gt;] local_bh_enable_ip+0x98/0xec\n[] ieee80211_alloc_node+0x29c/0x47c [umac]\n[] ieee80211_reset_bss+0x58/0x154 [umac]\n[] ieee80211_vap_attach+0x20/0x68 [umac]\n[] ath_vap_create+0x430/0x6b0 [umac]\n[] wlan_vap_create+0x58/0x210 [umac]\n[] osif_ioctl_create_vap+0x268/0x790 [umac]\n[] ath_ioctl+0x134/0x94c [umac]\n[\u0026lt;8022db50\u0026gt;] dev_ioctl+0x28c/0x88\n现在想定位指令：\n[\u0026lt;80041f10\u0026gt;] local_bh_enable_ip+0x98/0xec\n可以这样做：\n$ mips-linux-gdb vmlinux\nGNU gdb 6.8\nCopyright (coffee) 2008 Free Software Foundation, Inc.\nLicense GPLv3+: GNU GPL version 3 or later http://gnu.org/licenses/gpl.html\nThis is free software: you are free to change and redistribute it.\nThere is NO WARRANTY, to the extent permitted by law. Type \u0026ldquo;show copying\u0026rdquo;\nand \u0026ldquo;show warranty\u0026rdquo; for details.\nThis GDB was configured as \u0026ldquo;\u0026ndash;host=i386-pc-linux-gnu \u0026ndash;target=mips-linux- uclibc\u0026rdquo;\u0026hellip;\n输入命令\n(gdb) list *(local_bh_enable_ip+0x98)\ngdb返回结果\n0x80041f10 is in local_bh_enable_ip (kernel/softirq.c:216).\n211\n212 EXPORT_SYMBOL(_local_bh_enable);\n213\n214 static inline void _local_bh_enable_ip(unsigned long ip)\n215 {\n216 WARN_ON_ONCE(in_irq() || irqs_disabled());\n217 #ifdef CONFIG_TRACE_IRQFLAGS\n218 local_irq_disable();\n219 #endif\n220 /*\n(gdb)\n","permalink":"https://lewang.dev/posts/2012-11-06-how_to_use_gdb_to_find_the_kernel_source_code/","summary":"最近做一些Kernel方面的工作，从一头雾水开始，多做些记录吧。\naddr2line也可以根据指令地址定位C代码对应的行，但是对于Kernel module却不是很方便，使用gdb就要容易得多。\nNOTE：在使用gdb定位C代码之前需要开启-g选项编译内核或者module\n例如有这样的Call Trace\nCall Trace:\n[\u0026lt;8033265c\u0026gt;] dump_stack+0x8/0x30\n[\u0026lt;8003abbc\u0026gt;] warn_slowpath_common+0x70/0x98\n[\u0026lt;80041f10\u0026gt;] local_bh_enable_ip+0x98/0xec\n[] ieee80211_alloc_node+0x29c/0x47c [umac]\n[] ieee80211_reset_bss+0x58/0x154 [umac]\n[] ieee80211_vap_attach+0x20/0x68 [umac]\n[] ath_vap_create+0x430/0x6b0 [umac]\n[] wlan_vap_create+0x58/0x210 [umac]\n[] osif_ioctl_create_vap+0x268/0x790 [umac]\n[] ath_ioctl+0x134/0x94c [umac]\n[\u0026lt;8022db50\u0026gt;] dev_ioctl+0x28c/0x88\n现在想定位指令：\n[\u0026lt;80041f10\u0026gt;] local_bh_enable_ip+0x98/0xec\n可以这样做：\n$ mips-linux-gdb vmlinux\nGNU gdb 6.8\nCopyright (coffee) 2008 Free Software Foundation, Inc.\nLicense GPLv3+: GNU GPL version 3 or later http://gnu.org/licenses/gpl.html\nThis is free software: you are free to change and redistribute it.","title":"使用gdb根据指令地址定位到C代码对应的行"},{"content":"几个简单适合小程序的Makefile，可直接拷贝使用，自己mark一下，这样长时间不用Makefile也不用去看手册了\n0.只有一个文件\n变量的定义，方便使用 CC = gcc\nCCFLAGS = -O2 -s\nBIN = test\nOBJ = root_password_tool_win32.o\n$(BIN) : $(OBJ)\n$(CC) $(CCFLAGS) -o test $(OBJ)\n$(OBJ) : root_password_tool_win32.c\n$(CC) $(CCFLAGS) -c root_password_tool_win32.c\n.PHONY : clean\nclean :\n-rm $(BIN) $(OBJ)\n1.简单编译命令行程序，$(CC)前面是tab，不是space\n一共五个文件：\ntest.c\nmd5.c md5.h\npassword.c password.h\nmain函数在test.c中，test.c文件include了md5.h和password.h\nCC = gcc\nCCFLAGS = -O2 -s\nBIN = password\nOBJ = md5.o password.o test.o\n$(BIN) : $(OBJ)\n$(CC) $(CCFLAGS) -o password $(OBJ)\nmd5.o : md5.c md5.h\n$(CC) $(CCFLAGS) -c md5.c\npassword.o : password.c password.h\n$(CC) $(CCFLAGS) -c password.c\ntest.o : test.c\n$(CC) $(CCFLAGS) -c test.c\n.PHONY : clean\nclean :\n-rm $(BIN) $(OBJ)\nmake # 编译得到password.exe，这里在windows下cigwin+mingw环境中编译\nmake clean # 清理编译生成的*.o等文件\n2.使用g++编译win32程序的Makefile，其中简写了一些，可以对比1看看不同之处\n说明：\n(0)指定静态库目录，连接静态库libcomctl32.a编译程序\n(1)编译windows资源文件(使用mingw的windres)\n(2)使用-mwindows编译选项去除弹出的cmd窗口\nCC = g++\n-O2 : optimization option -s : build small binary -mwindows : use this option to remove the popping cmd window CCFLAGS = -O2 -s -mwindows\nBIN = test\nWINDRES = windres\nRES = resource.o\nOBJ = main.o md5.o password.o $(RES)\nwhere is your mingw library? LIBPATH = \u0026lsquo;C:\\Program Files\\CodeBlocks\\MinGW\\lib'\nLIBS=-L$(LIBPATH) -lcomctl32\nRM = -rm\n$(BIN): $(OBJ)\n$(CC) $(CCFLAGS) -o $(BIN) $(OBJ) $(LIBS)\nmain.o: main.cpp\nmd5.o: md5.cpp md5.h\npassword.o: password.cpp password.h\n编译资源文件 $(RES): resource.rc rpt.ico manifest\n$(WINDRES) -o $(RES) resource.rc\n.PHONY:clean\nclean:\n$(RM) $(BIN) $(OBJ)\n3.编译静态库\n其中一些$\u0026lt;，$^, $@是Makefile的自动化变量，详细了解可以看《跟我一起写Makefile》的第五章\nCC = g++\nOBJS = utp.o utp_utils.o\n-g : for debug CFLAGS = -g -O2 -Wall -DPOSIX\nTARGET = libutp.a\n.cpp.o:\n$(CC) -c $(CFLAGS) $\u0026lt;\nall: $(TARGET)\nlibutp.a: $(OBJS)\nar cru $@ $^\nranlib $@\n.PHONY : clean\nclean :\n-rm *.o $(TARGET)\n4.编译动态库\n(0)windows下使用mingw编译dll动态库\nCC = g++\nOBJS = utp.o utp_utils.o\nCCFLAGS = -fno-exceptions -fno-rtti -Wall -g -lwsock\nTARGET = libutp.dll\nall: $(TARGET)\n$(TARGET): $(OBJS)\n-rm -f $(TARGET)\n$(CC) -shared -o $(TARGET) $(OBJS) -lws2_32\n.cpp.o:\n$(CC) -c -DPOSIX -fpic -I . -I utp_config_lib $(CCFLAGS) $\u0026lt;\n.PHONY: clean\nclean:\n-rm -f $(OBJS) $(TARGET)\n(1)linux下编译so动态库\nCC = g++\nOBJS = utp.o utp_utils.o\nCCFLAGS = -fno-exceptions -fno-rtti -Wall -g\nTARGET = libutp.so\nall: $(TARGET)\n$(TARGET): $(OBJS)\n-rm -f $(TARGET)\ng++ -shared -o $(TARGET) $(OBJS)\n.cpp.o:\ng++ -c -DPOSIX -fpic -I . -I utp_config_lib $(CCFLAGS) $\u0026lt;\n.PHONY: clean\nclean:\n-rm -f $(OBJS) CCFLAGS\n5.编译多个目标，0-2均是编译单个目标\n(0) 添加头文件目录\n(1) 使用自己编译的静态库libutp.a\nCC = g++\nCFLAGS = -g\nLIBUTP_PATH = /home/actiontec/workspace/code_reading/third_party/libutp/lib\nINCLUDES = -I${LIBUTP_PATH}\nLIBS = -L$(LIBUTP_PATH) -lutp -lpthread -lrt\nTARGET = all\nall: server client\nserver: server.cpp\n$(CC) $(CFLAGS) -o $@ $^ $(INCLUDES) $(LIBS)\nclient: client.cpp\n$(CC) $(CFLAGS) -o $@ $^ $(INCLUDES) $(LIBS)\n.PHONY : clean\nclean:\n-rm *.o server client\n以上仅供参考\n","permalink":"https://lewang.dev/posts/2012-11-06-quick_makefile/","summary":"几个简单适合小程序的Makefile，可直接拷贝使用，自己mark一下，这样长时间不用Makefile也不用去看手册了\n0.只有一个文件\n变量的定义，方便使用 CC = gcc\nCCFLAGS = -O2 -s\nBIN = test\nOBJ = root_password_tool_win32.o\n$(BIN) : $(OBJ)\n$(CC) $(CCFLAGS) -o test $(OBJ)\n$(OBJ) : root_password_tool_win32.c\n$(CC) $(CCFLAGS) -c root_password_tool_win32.c\n.PHONY : clean\nclean :\n-rm $(BIN) $(OBJ)\n1.简单编译命令行程序，$(CC)前面是tab，不是space\n一共五个文件：\ntest.c\nmd5.c md5.h\npassword.c password.h\nmain函数在test.c中，test.c文件include了md5.h和password.h\nCC = gcc\nCCFLAGS = -O2 -s\nBIN = password\nOBJ = md5.o password.o test.o\n$(BIN) : $(OBJ)\n$(CC) $(CCFLAGS) -o password $(OBJ)","title":"几个简单的Makefile"},{"content":"好久没有在这里写点东西了，要养成书写记录的习惯。\n这里简单的讨论一下java设计网络程序中如何控制上传和下载速度，我们常见的FTP，HTTP，BT等协议都是TCP的，但是现在流行的utorrent却基于UDP实现了自己UTP协议(UDP+拥塞控制)，不管使用什么协议，站在I/O的角度来说，限速的控制思路都是一样的。\n思路很简单，如下：\n1.假设下载或者上传速度上限是m (KB/s),那么发送一个固定的字节数据(假设是n字节)的时间花费是：n/m；\n2.假设现在要发送n字节的数据，那么理论所需的时间应该是n/m，而在实际情况下，发送n字节的数据只花费了t秒，那么发送该发送线程就应该睡眠n/m-t秒，这样就基本实现了速度的控制。\n代码以TCP为例\n速度控制\n1 package com.actiontec.net.bandwidth;\n2\n3 /**\n4 *\n5 * @author Le\n6 *\n7 /\n8 public class BandwidthLimiter {\n9\n10 / KB /\n11 private static Long KB = 1024l;\n12\n13 / The smallest count chunk length in bytes /\n14 private static Long CHUNK_LENGTH = 1024l;\n15\n16 / How many bytes will be sent or receive /\n17 private int bytesWillBeSentOrReceive = 0;\n18\n19 / When the last piece was sent or receive /\n20 private long lastPieceSentOrReceiveTick = System.nanoTime();\n21\n22 / Default rate is 1024KB/s /\n23 private int maxRate = 1024;\n24\n25 / Time cost for sending CHUNK_LENGTH bytes in nanoseconds /\n26 private long timeCostPerChunk = (1000000000l * CHUNK_LENGTH)\n27 / (this.maxRate * KB);\n28\n29 /*\n30 * Initialize a BandwidthLimiter object with a certain rate.\n31 *\n32 * @param maxRate\n33 * the download or upload speed in KBytes\n34 /\n35 public BandwidthLimiter(int maxRate) {\n36 this.setMaxRate(maxRate);\n37 }\n38\n39 /*\n40 * Set the max upload or download rate in KB/s. maxRate must be grater than\n41 * 0. If maxRate is zero, it means there is no bandwidth limit.\n42 *\n43 * @param maxRate\n44 * If maxRate is zero, it means there is no bandwidth limit.\n45 * @throws IllegalArgumentException\n46 /\n47 public synchronized void setMaxRate(int maxRate)\n48 throws IllegalArgumentException {\n49 if (maxRate \u0026lt; 0) {\n50 throw new IllegalArgumentException(\u0026ldquo;maxRate can not less than 0\u0026rdquo;);\n51 }\n52 this.maxRate = maxRate \u0026lt; 0 ? 0 : maxRate;\n53 if (maxRate == 0)\n54 this.timeCostPerChunk = 0;\n55 else\n56 this.timeCostPerChunk = (1000000000l * CHUNK_LENGTH)\n57 / (this.maxRate * KB);\n58 }\n59\n60 /*\n61 * Next 1 byte should do bandwidth limit.\n62 /\n63 public synchronized void limitNextBytes() {\n64 this.limitNextBytes(1);\n65 }\n66\n67 /*\n68 * Next len bytes should do bandwidth limit\n69 *\n70 * @param len\n71 /\n72 public synchronized void limitNextBytes(int len) {\n73 this.bytesWillBeSentOrReceive += len;\n74\n75 / We have sent CHUNK_LENGTH bytes */\n76 while (this.bytesWillBeSentOrReceive \u0026gt; CHUNK_LENGTH) {\n77 long nowTick = System.nanoTime();\n78 long missedTime = this.timeCostPerChunk\n79 - (nowTick - this.lastPieceSentOrReceiveTick);\n80 if (missedTime \u0026gt; 0) {\n81 try {\n82 Thread.sleep(missedTime / 1000000,\n83 (int) (missedTime % 1000000));\n84 } catch (InterruptedException e) {\n85 e.printStackTrace();\n86 }\n87 }\n88 this.bytesWillBeSentOrReceive -= CHUNK_LENGTH;\n89 this.lastPieceSentOrReceiveTick = nowTick\n90 + (missedTime \u0026gt; 0 ? missedTime : 0);\n91 }\n92 }\n93 }\n94\n下载控制\n1 package com.actiontec.net.bandwidth;\n2\n3 import java.io.IOException;\n4 import java.io.InputStream;\n5\n6 /**\n7 * @author Le\n8 *\n9 */\n10 public class DownloadLimiter extends InputStream {\n11 private InputStream is = null;\n12 private BandwidthLimiter bandwidthLimiter = null;\n13\n14 public DownloadLimiter(InputStream is, BandwidthLimiter bandwidthLimiter)\n15 {\n16 this.is = is;\n17 this.bandwidthLimiter = bandwidthLimiter;\n18 }\n19 @Override\n20 public int read() throws IOException {\n21 if(this.bandwidthLimiter != null)\n22 this.bandwidthLimiter.limitNextBytes();\n23 return this.is.read();\n24 }\n25\n26 public int read(byte b[], int off, int len) throws IOException\n27 {\n28 if (bandwidthLimiter != null)\n29 bandwidthLimiter.limitNextBytes(len);\n30 return this.is.read(b, off, len);\n31 }\n32 }\n同样，上传控制\n1 package com.actiontec.net.bandwidth;\n2\n3 import java.io.IOException;\n4 import java.io.OutputStream;\n5\n6 /**\n7 * @author Le\n8 *\n9 */\n10 public class UploadLimiter extends OutputStream {\n11 private OutputStream os = null;\n12 private BandwidthLimiter bandwidthLimiter = null;\n13\n14 public UploadLimiter(OutputStream os, BandwidthLimiter bandwidthLimiter)\n15 {\n16 this.os = os;\n17 this.bandwidthLimiter = bandwidthLimiter;\n18 }\n19\n20 @Override\n21 public void write(int b) throws IOException {\n22 if (bandwidthLimiter != null)\n23 bandwidthLimiter.limitNextBytes();\n24 this.os.write(b);\n25 }\n26\n27 public void write(byte[] b, int off, int len) throws IOException {\n28 if (bandwidthLimiter != null)\n29 bandwidthLimiter.limitNextBytes(len);\n30 this.os.write(b, off, len);\n31 }\n32\n33 }\n对于一个TCP socket\n1 ServerSocket socket = new ServerSocket();\n2 //其它初始化略\n1 //从socket中以一定的速率读数据\n2 //java 3 DownloadLimiter dl = new DownloadLimiter(socket.getInputStream(), new BandwidthLimiter(6250)); 4 is = new DataInputStream(dl); 5 6 //读数据 7 int len = is.readInt(); 8 ByteBuffer buffer = ByteBuffer.allocate(4 + len); 9 buffer.putInt(len); 10 is.readFully(buffer.array(), 4, buffer.remaining()); 11 //\n12\n13 //以一定的速率写数据到socket\n14 //java 15 UploadLimiter ul = new UploadLimiter(socket.getOutputStream(), new BandwidthLimiter(6250)); 16 ul.write(![](http://www.blogjava.net/Images/dot.gif)); 17 //\n在多线程环境下也可以使用上述的方法。最后附图是任务管理器的网络利用率图6250KB/s（也就是50000kb/s,附图中网络利用率也在5%左右，所以应该这个做法还算准确）\n","permalink":"https://lewang.dev/posts/2012-10-18-bandwidthlimiter/","summary":"好久没有在这里写点东西了，要养成书写记录的习惯。\n这里简单的讨论一下java设计网络程序中如何控制上传和下载速度，我们常见的FTP，HTTP，BT等协议都是TCP的，但是现在流行的utorrent却基于UDP实现了自己UTP协议(UDP+拥塞控制)，不管使用什么协议，站在I/O的角度来说，限速的控制思路都是一样的。\n思路很简单，如下：\n1.假设下载或者上传速度上限是m (KB/s),那么发送一个固定的字节数据(假设是n字节)的时间花费是：n/m；\n2.假设现在要发送n字节的数据，那么理论所需的时间应该是n/m，而在实际情况下，发送n字节的数据只花费了t秒，那么发送该发送线程就应该睡眠n/m-t秒，这样就基本实现了速度的控制。\n代码以TCP为例\n速度控制\n1 package com.actiontec.net.bandwidth;\n2\n3 /**\n4 *\n5 * @author Le\n6 *\n7 /\n8 public class BandwidthLimiter {\n9\n10 / KB /\n11 private static Long KB = 1024l;\n12\n13 / The smallest count chunk length in bytes /\n14 private static Long CHUNK_LENGTH = 1024l;\n15\n16 / How many bytes will be sent or receive /","title":"Java程序如何限速(控制下载和上传速度)"},{"content":"资源： __ __\nPC A: Ubuntu 12.04 LTS，两块网卡，分别为eth0和eth1，使用eth0连接 互联网 __ __\nPC B: Windows 7, 一块网卡，为eth0 __ __\n目标： __ __\n使得B通过A连接互联网(IPv4)，即实现A为路由器的功能 __ __\n步骤： __ __\nPC A: __ __\n(0)假设eth0已经配置好，PC可以通过eth0连接互联网 ，配置eth1为，如/etc/network/interface所示 __ __\nauto lo __ __\niface lo inet loopback __ __\nauto eth0 __ __\niface eth0 inet static __ __\naddress 172.16.10.65 __ __\ngateway 172.16.10.254 __ __\nnetmask 255.255.0.0 __ __\nauto eth1 __ __\niface eth1 inet static __ __\naddress 192.168.6.1 __ __\nnetmask 255.255.255.0 __ __\nnetwork 192.168.6.0 __ __\nbroadcast 192.168.6.255 __ __\n当然，我是删除了network-manager, network-manager-gnome的才可以通过配置interface这个文件来设置网络的 __ __\n你也可以通过network-manager来配置。DNS服务 器可以通过/etc/resolv.conf来配置： __ __\nnameserver 168.95.1.1 #这是我的DNS服务器，你可以配置成你可以访问的，如8.8. 8.8 __ __\n重启你的网络服务或者重启PC A，以上设置就可以生效。 __ __\n(1)Linux系统自身就有IPv4包转发的功能，在/ etc/sysctl.conf进行配置， __ __\n打开IPv4转发功能: __ __\nnet.ipv4.ip_forward=1 __ __\n输入以下命令使得上面的设置生效(或者重启电脑)： __ __\nsudo sysctl -p __ __\n(2)通过iptable实现IPv4包转发 __ __\nsudo iptables -t nat -A POSTROUTING -s192.168.6.0/24 -o eth0 -j MASQUERADE __ __\n可以通过iptables-save将iptables rule都保存到文件中，在通过iptables- restore读取rule到iptables中，\n可以自己写一个脚本来实现iptables自动的载入rule， 或者找到iptables的配置文件来修改 __ __\nPC B __ __\n(0)将PC B的eth0与PC A的eth1用网线直接连接，配置PC B的eth0为： __ __\nIP ADDRESS : 192.168.6.101 __ __\nSUBNET MASK: 255.255.255.0 __ __\nGATE WAY : 192.168.1.1i __ __\nDNS Server : 168.95.1.1 __ __\n这样PC B就可以通过PC A连接互联网了 __ __\n增强配置： __ __\n路由器都可以自动给PC分配IP，而不需要手动配置IP， 这是通过DHCP来实现的。如果也要实现相同的功能，那 __ __\n就需要在PC A上搭建一个DHCP服务器。 __ __\n(0)安装DHCP服务器 __ __\nsudo apt-get install isc-dhcp-common isc-dhcp-server __ __\n(1)配置DHCP __ __\n(i)修改/etc/default/isc-dhcp- server __ __\nINTERFACES=\u0026ldquo;eth1\u0026rdquo; __ __\n(ii)修改/etc/dhcp/dhcpd.conf __ __\nsubnet 192.168.6.0 netmask 255.255.255.0 { __ __\nrange 192.168.6.100 192.168.6.200; __ __\noption routers 192.168.6.1; __ __\noption broadcast-address 192.168.6.255; __ __\noption domain-name-servers 168.95.1.1; __ __\ndefault-lease-time 600; __ __\nmax-lease-time 7200; __ __\n} __ __\n(2)PC B可以自动获取IP了，如果在PC A的eth1上接一个switch，接在switch上所有的P C都可以获取IP了\n","permalink":"https://lewang.dev/posts/2012-08-22-router-dhcp/","summary":"资源： __ __\nPC A: Ubuntu 12.04 LTS，两块网卡，分别为eth0和eth1，使用eth0连接 互联网 __ __\nPC B: Windows 7, 一块网卡，为eth0 __ __\n目标： __ __\n使得B通过A连接互联网(IPv4)，即实现A为路由器的功能 __ __\n步骤： __ __\nPC A: __ __\n(0)假设eth0已经配置好，PC可以通过eth0连接互联网 ，配置eth1为，如/etc/network/interface所示 __ __\nauto lo __ __\niface lo inet loopback __ __\nauto eth0 __ __\niface eth0 inet static __ __\naddress 172.16.10.65 __ __\ngateway 172.16.10.254 __ __\nnetmask 255.255.0.0 __ __\nauto eth1 __ __","title":"把ubuntu配置成路由器+DHCP"},{"content":"前一篇帖子使用了 xpath，后来了解到 xpath 的性能可能比较差，而且在遇到有命名空间的时候还有点小麻烦。这里都已豆瓣提供的 API 返回的 xml 为例。豆瓣返回的 xml 不是 rss 格式的，而是 Atom 格式，有很多的命名空间，如果习惯于操作 rss 格式的 xml，那么可以直接把 xml 里面的命名空间的字符串都 replace 掉，这个方法貌似很简单很暴力，就不会遇到命名空间的问题了。\n从豆瓣 API 开始，点击这个链接 http://api.douban.com/book/subjects?q=java 你就可以看到 xml 格式，由于返回的是 Atom 格式的 xml，浏览器会自动帮你解析，所以应该这样\n# 这样就可以得到一个java.xml文件。 $ wget -O java.xml http://api.douban.com/book/subjects?q=java 这样就可以得到一个 java.xml 文件，接下来使用 SimplePHP 处理这个文件。\n首先要创建一个 SimpleXML 对象\n//$content为xml的内容 $xml = new SimpleXMLElement($content); 现在要获取 opensearch 命名空间下的 totalResults 节点的文本，代码如下：\n$children = $xml-\u0026gt;children(\u0026#39;http://a9.com/-/spec/opensearchrss/1.0/\u0026#39;); $totalResults = $children-\u0026gt;totalResults; 处理所有 xml 文档中所有的 entry 节点\n//处理所有找到的记录 $entries = $xml-\u0026gt;entry; foreach($entries as $entry){ //取命名空间下的孩子节点 $entryChildren = $entry-\u0026gt;children(\u0026#39;http://www.douban.com/xmlns/\u0026#39;); //... } 好了，这里只是抛砖引玉，其它具体的操作还得参考 SimpleXML 的文档。\n","permalink":"https://lewang.dev/posts/2012-03-22-a-short-discuss-of-simplexml-of-php/","summary":"前一篇帖子使用了 xpath，后来了解到 xpath 的性能可能比较差，而且在遇到有命名空间的时候还有点小麻烦。这里都已豆瓣提供的 API 返回的 xml 为例。豆瓣返回的 xml 不是 rss 格式的，而是 Atom 格式，有很多的命名空间，如果习惯于操作 rss 格式的 xml，那么可以直接把 xml 里面的命名空间的字符串都 replace 掉，这个方法貌似很简单很暴力，就不会遇到命名空间的问题了。\n从豆瓣 API 开始，点击这个链接 http://api.douban.com/book/subjects?q=java 你就可以看到 xml 格式，由于返回的是 Atom 格式的 xml，浏览器会自动帮你解析，所以应该这样\n# 这样就可以得到一个java.xml文件。 $ wget -O java.xml http://api.douban.com/book/subjects?q=java 这样就可以得到一个 java.xml 文件，接下来使用 SimplePHP 处理这个文件。\n首先要创建一个 SimpleXML 对象\n//$content为xml的内容 $xml = new SimpleXMLElement($content); 现在要获取 opensearch 命名空间下的 totalResults 节点的文本，代码如下：\n$children = $xml-\u0026gt;children(\u0026#39;http://a9.com/-/spec/opensearchrss/1.0/\u0026#39;); $totalResults = $children-\u0026gt;totalResults; 处理所有 xml 文档中所有的 entry 节点\n//处理所有找到的记录 $entries = $xml-\u0026gt;entry; foreach($entries as $entry){ //取命名空间下的孩子节点 $entryChildren = $entry-\u0026gt;children(\u0026#39;http://www.","title":"再说 PHP 中 SimpleXML"},{"content":"这句话是 FACEBOOK 的马克·扎克伯格说的。\n我很认同，并且感觉自己也是这么做的。现在自己做得很多事情，从来没有考虑过它会不会值钱，只是感觉把自己的想法做出来，变成实物的过程就已经很酷。\n对于酷，我觉得至少有两方面的意义。\n一种酷，是一种自我实现的感觉，是一种认同感，成就感。比如自己把自己的不错的想法实现了，当自己看到自己作品的时候，肯定觉得这种感觉很酷。接着你的作品有很多人用了，看了，然后给与了肯定或者赞扬的评价，这时候感到酷，我觉得就应该是一种认同感和成就感。都是内心的感觉，一种推进自我完善的感觉。\n还有一种酷，那就是表面上的酷，比如你的网页效果做得很酷很炫，你实现的功能很酷。虽然这些是表面上的酷，但这些酷的背后却隐藏着无数的思考和磨练。这么表面上的酷，却恰恰可以给人带来内心上的酷。\n就拿摆摊网来说，小小的网站，做得很简单。但是为了实现页面小小的效果，还不得不去多学点东西多花些时间。我喜欢自己东西能弄得那种能给人至简至美的感觉，但是却不能因为简而不“美”了，这里的美是给人一种更好的感觉。所以自己总是在觉得不破坏简的前提下，尽量的让自己的应用让人使用的体验更好更酷。比如说发布书籍信息的页面，以前必须使用 ISBN 号，现在这里点击查看的将要改成 @林风琦 推荐的 chegg 的效果，是不是比豆瓣的书籍检索要酷很多。进一步思考，对于摆摊首页的检索，以后是不是应该结合用户的搜索习惯和本身的书籍物品数据也实现这种 AutoComplete 功能。还比如说摆摊还将开发 Android 手机客户端，到时候只要一拍，就可以把书信息扫下来了，看，多酷！\n现在自己的科研也是这样，能给自己带来一种很酷的感觉，但是就科研的实际应用来说，我觉得可以应用的范围很小。但是想着能把 Jim Gray 这位数据库大神[此大神已经被外星人带走了，详情看这里]负责的基于微软 SQL Server 的 SDSS 项目迁移到适合科学运算的数组模型的数据库上来，然后我们还有可能就性能 PK 一把，想想就觉得很酷。更酷的是，自己还想到了一种自动迁移的方法[还没有经过深思和验证]。反正就是觉得酷儿！\n为了更酷，继续努力！\n我是 A Lucky Apple，@小码哥\n","permalink":"https://lewang.dev/posts/2012-03-13-things-i-create-must-be-cool/","summary":"这句话是 FACEBOOK 的马克·扎克伯格说的。\n我很认同，并且感觉自己也是这么做的。现在自己做得很多事情，从来没有考虑过它会不会值钱，只是感觉把自己的想法做出来，变成实物的过程就已经很酷。\n对于酷，我觉得至少有两方面的意义。\n一种酷，是一种自我实现的感觉，是一种认同感，成就感。比如自己把自己的不错的想法实现了，当自己看到自己作品的时候，肯定觉得这种感觉很酷。接着你的作品有很多人用了，看了，然后给与了肯定或者赞扬的评价，这时候感到酷，我觉得就应该是一种认同感和成就感。都是内心的感觉，一种推进自我完善的感觉。\n还有一种酷，那就是表面上的酷，比如你的网页效果做得很酷很炫，你实现的功能很酷。虽然这些是表面上的酷，但这些酷的背后却隐藏着无数的思考和磨练。这么表面上的酷，却恰恰可以给人带来内心上的酷。\n就拿摆摊网来说，小小的网站，做得很简单。但是为了实现页面小小的效果，还不得不去多学点东西多花些时间。我喜欢自己东西能弄得那种能给人至简至美的感觉，但是却不能因为简而不“美”了，这里的美是给人一种更好的感觉。所以自己总是在觉得不破坏简的前提下，尽量的让自己的应用让人使用的体验更好更酷。比如说发布书籍信息的页面，以前必须使用 ISBN 号，现在这里点击查看的将要改成 @林风琦 推荐的 chegg 的效果，是不是比豆瓣的书籍检索要酷很多。进一步思考，对于摆摊首页的检索，以后是不是应该结合用户的搜索习惯和本身的书籍物品数据也实现这种 AutoComplete 功能。还比如说摆摊还将开发 Android 手机客户端，到时候只要一拍，就可以把书信息扫下来了，看，多酷！\n现在自己的科研也是这样，能给自己带来一种很酷的感觉，但是就科研的实际应用来说，我觉得可以应用的范围很小。但是想着能把 Jim Gray 这位数据库大神[此大神已经被外星人带走了，详情看这里]负责的基于微软 SQL Server 的 SDSS 项目迁移到适合科学运算的数组模型的数据库上来，然后我们还有可能就性能 PK 一把，想想就觉得很酷。更酷的是，自己还想到了一种自动迁移的方法[还没有经过深思和验证]。反正就是觉得酷儿！\n为了更酷，继续努力！\n我是 A Lucky Apple，@小码哥","title":"我想做的东西可以不值钱，但是它必须‘酷’"},{"content":"今天组会不用开了，报告也不用做了，感觉比较爽，于是晚上的时候决定给摆摊写一些新的东西。\n于是刚刚给摆摊的首页添加了一个小小的功能，就是能够随机的选取5本书在首页上循环展示，另外修正了一些网友提出的问题。\n首页图片轮换是用jquery实现的，做法也很简单，当然实现的功能和效果还是比较弱的，这里是我用到一些资料。 CSS定位：http://www.haozi.cn/code/position-static-relative-absolute-float/\n好了，摆摊这几天暂时就这个样子了，得放下心思继续去搞自己的科研了。\n过段时间一定要好好总结一下，写一些关于数组数据库系列的文章。\n","permalink":"https://lewang.dev/posts/2012-03-10-at-a-late-night/","summary":"今天组会不用开了，报告也不用做了，感觉比较爽，于是晚上的时候决定给摆摊写一些新的东西。\n于是刚刚给摆摊的首页添加了一个小小的功能，就是能够随机的选取5本书在首页上循环展示，另外修正了一些网友提出的问题。\n首页图片轮换是用jquery实现的，做法也很简单，当然实现的功能和效果还是比较弱的，这里是我用到一些资料。 CSS定位：http://www.haozi.cn/code/position-static-relative-absolute-float/\n好了，摆摊这几天暂时就这个样子了，得放下心思继续去搞自己的科研了。\n过段时间一定要好好总结一下，写一些关于数组数据库系列的文章。","title":"在这安静的夜里"},{"content":"我期待 2012 的到来，这一年我将有一个新的开始，好期待。\n最近一直在忙着做实验，读论文，都没有什么时间来弄弄摆摊，自己心里的那种对于摆摊的激情也渐渐淡了很多，但是想想自己当初的想法，立马又有动力拾起摆摊。\n一个小小的网站，看起来确实很简单，也就是拿 php 连连数据库，做做一些简单的操作，可自己在写这个网站的时候总是很慢。时间少而且比较凌乱是一个原因，但是总的还是缺少一个全面完善细致的规划，自己大部分也是照着当初的设计，然后想到哪儿做到哪儿，如今的摆摊，已经和当初的相去甚远了。越写越复杂，问题也越来越多，停下来一想，还是挥起大刀把乱乱的东西都砍了吧，弄得越简单越好。\n加油！过几天就要回家了，不知道什么时候才能做好，让自己心里舒服一下。这几天赶紧将域名绑定好，将人人、新浪微博等的接入做好，大概就这个样子了。\n大家新年快乐！\n","permalink":"https://lewang.dev/posts/2012-01-09-for-baigetan-2012/","summary":"我期待 2012 的到来，这一年我将有一个新的开始，好期待。\n最近一直在忙着做实验，读论文，都没有什么时间来弄弄摆摊，自己心里的那种对于摆摊的激情也渐渐淡了很多，但是想想自己当初的想法，立马又有动力拾起摆摊。\n一个小小的网站，看起来确实很简单，也就是拿 php 连连数据库，做做一些简单的操作，可自己在写这个网站的时候总是很慢。时间少而且比较凌乱是一个原因，但是总的还是缺少一个全面完善细致的规划，自己大部分也是照着当初的设计，然后想到哪儿做到哪儿，如今的摆摊，已经和当初的相去甚远了。越写越复杂，问题也越来越多，停下来一想，还是挥起大刀把乱乱的东西都砍了吧，弄得越简单越好。\n加油！过几天就要回家了，不知道什么时候才能做好，让自己心里舒服一下。这几天赶紧将域名绑定好，将人人、新浪微博等的接入做好，大概就这个样子了。\n大家新年快乐！","title":"写给 2012 的摆摊"},{"content":"这几天没心情看论文，于是开始写写摆摊网的代码。摆摊主要做高校二手书籍信息的分享，于是需要从豆瓣上获取图书信息，豆瓣提供了一个完整的 xml 来描述一本书，例如http://api.douban.com/book/subject/isbn/9787543639136，于是需要处理这个 xml 来获取相应信息，这里小码哥采用的是直接使用 xpath 来获取 xml 文档中相应的信息。摆摊使用了 sae，于是使用 SimpleXML 来处理 xml，加上自己还只是 PHP 的初学者，于是出现了一些问题。\n这里是一些学习资料：\nxpath:http://www.w3school.com.cn/xpath/ simplexml:http://cn2.php.net/manual/en/simplexmlelement.xpath.php 新建 SimpleXMLElement 对象，xml 文件内容在$content 中\n$bookFromDouban = new SimpleXMLElement($content); 直接查询，在命名空间 db 下的所有 attribute，且有 name 属性，以下 xpath 查询是没有问题的，\n$result = $bookFromDouban-\u0026gt;xpath(\u0026#34;//db:attribute[@name]\u0026#34;); 但是，这条查询就有问题了，如下，\n$result = $bookFromDouban-\u0026gt;xpath(\u0026#34;//link[@rel=\u0026#39;self\u0026#39;]|//link[@rel=\u0026#39;image\u0026#39;]\u0026#34;); 上面的代码表示在默认的命名空间里面查询 link 节点，但是在 php 5.0 以上却不能查出数据。需要这样操作\n//获取这个xml的所有的命名空间 $namespaces = $bookFromDouban-\u0026gt;getNamespaces(); //注册默认的命名空间为\u0026#39;d\u0026#39; $bookFromDouban-\u0026gt;registerXPathNamespace(\u0026#39;d\u0026#39;, $namespaces[\u0026#39;\u0026#39;]); //现在这个查询就ok了 $result = $bookFromDouban-\u0026gt;xpath(\u0026#34;//d:link[@rel=\u0026#39;self\u0026#39;]|//d:link[@rel=\u0026#39;image\u0026#39;]\u0026#34;); 对$result 遍历就简单了，这里也给出一个简单的例子，遍历完，变量就初始化了。PS：代码写的不优雅，欢迎拍砖。\ntranslator = array(); $author = array(); $i = $j = 0; foreach($result as $item) { foreach($item-\u0026gt;attributes() as $val) { $val = str_replace(\u0026#39;-\u0026#39;,\u0026#39;_\u0026#39;,$val); if($val == \u0026#39;author\u0026#39;) { $author[$i++] = \u0026#39;\u0026#39;.$item; } else if($val == \u0026#39;translator\u0026#39;) translator[$j++] = \u0026#39;\u0026#39;.$item; else $$val = $item; } } ","permalink":"https://lewang.dev/posts/2011-12-01-how-to-use-simplexml-of-php/","summary":"这几天没心情看论文，于是开始写写摆摊网的代码。摆摊主要做高校二手书籍信息的分享，于是需要从豆瓣上获取图书信息，豆瓣提供了一个完整的 xml 来描述一本书，例如http://api.douban.com/book/subject/isbn/9787543639136，于是需要处理这个 xml 来获取相应信息，这里小码哥采用的是直接使用 xpath 来获取 xml 文档中相应的信息。摆摊使用了 sae，于是使用 SimpleXML 来处理 xml，加上自己还只是 PHP 的初学者，于是出现了一些问题。\n这里是一些学习资料：\nxpath:http://www.w3school.com.cn/xpath/ simplexml:http://cn2.php.net/manual/en/simplexmlelement.xpath.php 新建 SimpleXMLElement 对象，xml 文件内容在$content 中\n$bookFromDouban = new SimpleXMLElement($content); 直接查询，在命名空间 db 下的所有 attribute，且有 name 属性，以下 xpath 查询是没有问题的，\n$result = $bookFromDouban-\u0026gt;xpath(\u0026#34;//db:attribute[@name]\u0026#34;); 但是，这条查询就有问题了，如下，\n$result = $bookFromDouban-\u0026gt;xpath(\u0026#34;//link[@rel=\u0026#39;self\u0026#39;]|//link[@rel=\u0026#39;image\u0026#39;]\u0026#34;); 上面的代码表示在默认的命名空间里面查询 link 节点，但是在 php 5.0 以上却不能查出数据。需要这样操作\n//获取这个xml的所有的命名空间 $namespaces = $bookFromDouban-\u0026gt;getNamespaces(); //注册默认的命名空间为\u0026#39;d\u0026#39; $bookFromDouban-\u0026gt;registerXPathNamespace(\u0026#39;d\u0026#39;, $namespaces[\u0026#39;\u0026#39;]); //现在这个查询就ok了 $result = $bookFromDouban-\u0026gt;xpath(\u0026#34;//d:link[@rel=\u0026#39;self\u0026#39;]|//d:link[@rel=\u0026#39;image\u0026#39;]\u0026#34;); 对$result 遍历就简单了，这里也给出一个简单的例子，遍历完，变量就初始化了。PS：代码写的不优雅，欢迎拍砖。\ntranslator = array(); $author = array(); $i = $j = 0; foreach($result as $item) { foreach($item-\u0026gt;attributes() as $val) { $val = str_replace(\u0026#39;-\u0026#39;,\u0026#39;_\u0026#39;,$val); if($val == \u0026#39;author\u0026#39;) { $author[$i++] = \u0026#39;\u0026#39;.","title":"SimpleXML使用xpath"},{"content":"最近在为摆摊写点前端 js 的时候，在使用 jquery 的 $.ajax()这个方法的时候，当 datatype 设置为 json 时，总是不执行 success 回调，而是执行 error 回调。而将 datatype 设置成 text，就可以执行 success 回调了，摆摊使用的是 jQuery JavaScript Library v1.6.2，查看 jQuery 文档可知：\n“json”: Evaluates the response as JSON and returns a JavaScript object. In jQuery 1.4 the JSON data is parsed in a strict manner; any malformed JSON is rejected and a parse error is thrown. (See json.org for more information on proper JSON formatting.)\n我用 php 产生的字符串是\n{‘isbn’:\u0026#39;ISBN号码格式不正确，应为10位或者13位的字符串’} 很明显，json.org 规则使用的是双引号，而不是单引号，jQuery1.4 以后都要求更为严格的 json 格式\n对于以上单引号的字符串，可以使用 datatype 为 text，然后使用\n//content = {‘isbn’:\u0026#39;ISBN号码格式不正确，应为10位或者13位的字符串’} var json = eval(‘(‘+ content +’)\u0026#39;) ","permalink":"https://lewang.dev/posts/2011-12-01-when-datatype-is-json/","summary":"最近在为摆摊写点前端 js 的时候，在使用 jquery 的 $.ajax()这个方法的时候，当 datatype 设置为 json 时，总是不执行 success 回调，而是执行 error 回调。而将 datatype 设置成 text，就可以执行 success 回调了，摆摊使用的是 jQuery JavaScript Library v1.6.2，查看 jQuery 文档可知：\n“json”: Evaluates the response as JSON and returns a JavaScript object. In jQuery 1.4 the JSON data is parsed in a strict manner; any malformed JSON is rejected and a parse error is thrown. (See json.org for more information on proper JSON formatting.)\n我用 php 产生的字符串是","title":"jquery $.ajax() 中 datatype 为 json 时的问题"},{"content":"未来是个未知数，自己的未来自己都不知道会是什么样子，问别人，别人又怎么会知道你的未来。\n不要忘了自己心中想要的东西，即使是为了养家糊口而从事自己不喜欢的工作，也不要忘记自己希望做什么，静静的准备着，迟早有一天会爆发的。\n单纯的做着自己的业余爱好，业余爱好也就会发展成职业的。但是我不会忘了我心中想要去做什么，我会为之而积累准备。\n未来是个未知数，没有方程组能解开这个未知数，只能去试，试试才会知道。\n摆摊网的未来也是个未知数，很大的可能就是被淹没在数以亿计的小小站点之中，被互联网大潮一扫而过，什么也不会留下，但是，这就是我的爱好，我喜欢把自己的想法实现，不管别人喜不喜欢用，但是，我喜欢摆摊网，它是我想法的一个实体，我每次尝试的一个印记。\n自己的工作也定下来了，有接着研究自己的课题，但这几天不能安静下来搞自己的课题，总想着摆摊网还没有做好，于是拿出了两天，把摆摊的页面都做了出来，接下来就要做网站的逻辑部分了。\n网站很简单，但是要做好做的精致做得完美做得好用也是很不容易的。自己还是慢慢来搞，每天写点代码吧，还是要干些要紧的事儿，后天又得做报告了，得赶论文ppt了。\n期待有同学加入，一起来开发摆摊网，让摆摊网早点上线！！！\n要开始为了毕业而忙碌了。\n我是小码哥（新浪微博）\n","permalink":"https://lewang.dev/posts/2011-11-09-future-is-unknown/","summary":"未来是个未知数，自己的未来自己都不知道会是什么样子，问别人，别人又怎么会知道你的未来。\n不要忘了自己心中想要的东西，即使是为了养家糊口而从事自己不喜欢的工作，也不要忘记自己希望做什么，静静的准备着，迟早有一天会爆发的。\n单纯的做着自己的业余爱好，业余爱好也就会发展成职业的。但是我不会忘了我心中想要去做什么，我会为之而积累准备。\n未来是个未知数，没有方程组能解开这个未知数，只能去试，试试才会知道。\n摆摊网的未来也是个未知数，很大的可能就是被淹没在数以亿计的小小站点之中，被互联网大潮一扫而过，什么也不会留下，但是，这就是我的爱好，我喜欢把自己的想法实现，不管别人喜不喜欢用，但是，我喜欢摆摊网，它是我想法的一个实体，我每次尝试的一个印记。\n自己的工作也定下来了，有接着研究自己的课题，但这几天不能安静下来搞自己的课题，总想着摆摊网还没有做好，于是拿出了两天，把摆摊的页面都做了出来，接下来就要做网站的逻辑部分了。\n网站很简单，但是要做好做的精致做得完美做得好用也是很不容易的。自己还是慢慢来搞，每天写点代码吧，还是要干些要紧的事儿，后天又得做报告了，得赶论文ppt了。\n期待有同学加入，一起来开发摆摊网，让摆摊网早点上线！！！\n要开始为了毕业而忙碌了。\n我是小码哥（新浪微博）","title":"未来是个未知数"},{"content":"今天是十一假期归来的第一天，实验室也启用了新的管理制度，坐在北洋楼的实验室也有一会儿了，刚发了一会儿呆，对摆摊又有了一些新的思考。\n我的目标是想把摆摊做成高校的网上交易平台，也就是类似淘宝，但是作为摆摊网的初期是不合适的，因为这个交易平台太大太复杂。\n所以摆摊的第一步还是做交易信息的发布和检索，主打高校的书籍和物品交易信息，交易还是线下完成，这样，初期的摆摊应该就和赶集、百姓、58 等类似了，而且这样摆摊的实现难度也会大大降低，在加上 SNS 和 LBS，摆摊就可以提供比赶集、百姓、58 等更精准的信息服务了。\n这几天只是做了一些准备工作，熟悉了 sae 的环境，网站的架构也基本完成，页面上的设计也差不多了，接下来就是定下摆摊网的初步方向，确定第一阶段的核心功能，将其业务逻辑实现了。\n加油！\n","permalink":"https://lewang.dev/posts/2011-11-08-think-of-baigetan/","summary":"今天是十一假期归来的第一天，实验室也启用了新的管理制度，坐在北洋楼的实验室也有一会儿了，刚发了一会儿呆，对摆摊又有了一些新的思考。\n我的目标是想把摆摊做成高校的网上交易平台，也就是类似淘宝，但是作为摆摊网的初期是不合适的，因为这个交易平台太大太复杂。\n所以摆摊的第一步还是做交易信息的发布和检索，主打高校的书籍和物品交易信息，交易还是线下完成，这样，初期的摆摊应该就和赶集、百姓、58 等类似了，而且这样摆摊的实现难度也会大大降低，在加上 SNS 和 LBS，摆摊就可以提供比赶集、百姓、58 等更精准的信息服务了。\n这几天只是做了一些准备工作，熟悉了 sae 的环境，网站的架构也基本完成，页面上的设计也差不多了，接下来就是定下摆摊网的初步方向，确定第一阶段的核心功能，将其业务逻辑实现了。\n加油！","title":"关于摆摊的一些思考"},{"content":"从泰山回来这几天，抽得空把摆摊网的页面做了出来，包括 css 和 html 的编写等，现在可以从 baitan.sinaapp.com 看到页面效果了，目前兼容 ie8、ff、chrome、safari 等。\nbaitan001.com 也在备案之中，应该很快就可以使用 baitan001.com 域名访问了。\n有同学想练习 js，php 等 web 技术的，不妨联系我，我肯定能让你参与进来！\n我是小码哥\n","permalink":"https://lewang.dev/posts/2011-11-07-design-of-baigetan/","summary":"从泰山回来这几天，抽得空把摆摊网的页面做了出来，包括 css 和 html 的编写等，现在可以从 baitan.sinaapp.com 看到页面效果了，目前兼容 ie8、ff、chrome、safari 等。\nbaitan001.com 也在备案之中，应该很快就可以使用 baitan001.com 域名访问了。\n有同学想练习 js，php 等 web 技术的，不妨联系我，我肯定能让你参与进来！\n我是小码哥","title":"摆摊网页面设计"},{"content":"前几天一场大降温，把我给冻着了，肚子疼了好几天，还发烧，还好有个小盆友给我“打针喂药”，使得我这几天才得以康复，重现往日风采。\n本来这周五得做论文的报告，于是这周基本都在看论文。谁知这周又冒出了几个公司让我去面试，论文的报告又就此作罢，挪到下周。\n自己的外语很烂，基本没有投什么外企，昨天参加了微策略的面试之后，反而对自己的外语变得有些自信，最后和那位印度面试官聊天，他说我外语挺好，可是小盆友却说他那是在说客套话，不管客套不客套，反正我觉得挺开心，哈哈。。。\n这周末还有几个知名互联网公司的笔试面试，加油！\n关于摆摊，我还是抽出了一些时间来做了一些开发工作。先是看了赶集网吴石展杭州Qcon的关于MySQL数据库开发的三十六条军规，然后立马按照这些军规，对摆摊的数据库结构做了一些更改。此外，还开发了用户注册模块，只是页面还没有美化。\n还是一点一点的来，事情太多，还要搞科研毕业，加油！\n我是小码哥（新浪微博）\n","permalink":"https://lewang.dev/posts/2011-10-27-winter-is-coming/","summary":"前几天一场大降温，把我给冻着了，肚子疼了好几天，还发烧，还好有个小盆友给我“打针喂药”，使得我这几天才得以康复，重现往日风采。\n本来这周五得做论文的报告，于是这周基本都在看论文。谁知这周又冒出了几个公司让我去面试，论文的报告又就此作罢，挪到下周。\n自己的外语很烂，基本没有投什么外企，昨天参加了微策略的面试之后，反而对自己的外语变得有些自信，最后和那位印度面试官聊天，他说我外语挺好，可是小盆友却说他那是在说客套话，不管客套不客套，反正我觉得挺开心，哈哈。。。\n这周末还有几个知名互联网公司的笔试面试，加油！\n关于摆摊，我还是抽出了一些时间来做了一些开发工作。先是看了赶集网吴石展杭州Qcon的关于MySQL数据库开发的三十六条军规，然后立马按照这些军规，对摆摊的数据库结构做了一些更改。此外，还开发了用户注册模块，只是页面还没有美化。\n还是一点一点的来，事情太多，还要搞科研毕业，加油！\n我是小码哥（新浪微博）","title":"冬天来了"},{"content":"这段时间都在进行密集的笔试面试，但还是没有停下摆摊网的开发进程。目前摆摊网已经由吴江川同学设计好了产品的原型，我也在原型的基础上做好了全部页面的设计工作，接下来就是开始后台逻辑的开发。\n目前后台数据库部分已经设计完毕。\n加油！\n我是小码哥\n","permalink":"https://lewang.dev/posts/2011-10-21-process-of-baigetan/","summary":"这段时间都在进行密集的笔试面试，但还是没有停下摆摊网的开发进程。目前摆摊网已经由吴江川同学设计好了产品的原型，我也在原型的基础上做好了全部页面的设计工作，接下来就是开始后台逻辑的开发。\n目前后台数据库部分已经设计完毕。\n加油！\n我是小码哥","title":"摆摊的一些进展"},{"content":"早上睡到9点才起床，然后下楼载着小盆友去学三吃了早饭，一人吃了六个小包子，出了食堂发现天气真好，决定去拍些照片，然后又载着小盆友回到宿舍拿了相机。\n今天天气真好，微微的风，蓝蓝的天，枝头的树叶也变成了整片整片的金黄，好美！\n东西都已经收拾好了，明天一早出发，去泰山，看日出。\n我是小码哥\n","permalink":"https://lewang.dev/posts/2011-10-01-snapshot-of-10-1/","summary":"早上睡到9点才起床，然后下楼载着小盆友去学三吃了早饭，一人吃了六个小包子，出了食堂发现天气真好，决定去拍些照片，然后又载着小盆友回到宿舍拿了相机。\n今天天气真好，微微的风，蓝蓝的天，枝头的树叶也变成了整片整片的金黄，好美！\n东西都已经收拾好了，明天一早出发，去泰山，看日出。\n我是小码哥","title":"十一掠影"},{"content":"小碼哥，或樂哥\n过往与向往 兲朝哏都七里台职业技术学院相声专业(CS)硕士毕业 混迹六七八三个里台八年，走了一条学生、老师、学生曲折漫长的校园之路，除了一纸文凭之外，更是收获了爱情、友情和师生情 喜折腾，瞎折腾，机缘巧合来到魔都并定居在此 技校授得一技之长，努力成为一名匠人 联系方式 Email: atob('bGV3YW5nLmRldkBnbWFpbC5jb20=') 微博: @小码哥 Twitter: @lewangx GitHub: @lewangdev 关于本站 本网站使用 Hugo 静态博客生成器构建，托管在 Github Pages 服务上，本站 GitHub 仓库地址是 lewangdev.github.io。\n","permalink":"https://lewang.dev/about/","summary":"小碼哥，或樂哥\n过往与向往 兲朝哏都七里台职业技术学院相声专业(CS)硕士毕业 混迹六七八三个里台八年，走了一条学生、老师、学生曲折漫长的校园之路，除了一纸文凭之外，更是收获了爱情、友情和师生情 喜折腾，瞎折腾，机缘巧合来到魔都并定居在此 技校授得一技之长，努力成为一名匠人 联系方式 Email: atob('bGV3YW5nLmRldkBnbWFpbC5jb20=') 微博: @小码哥 Twitter: @lewangx GitHub: @lewangdev 关于本站 本网站使用 Hugo 静态博客生成器构建，托管在 Github Pages 服务上，本站 GitHub 仓库地址是 lewangdev.github.io。","title":"关于我"},{"content":" 点评：留在这里，让自己可以看到以往无知的自己\n一转眼，20 年就过去了，终于要和自己 20 年的学生身份说拜拜了。\n那天去图书馆借书，回来的路上敬业湖桥边的那棵树的时候，心不由得感叹起来，这棵树，我已经拍下了它的春夏秋冬，它叶枯叶落，相似年年，只因它生根于此，我是要走的。\n去图书馆，边走边想，就想到了前几天在知乎上看到的社会化图书馆网站，感觉很不错(而且 UI 做的很不错，想借鉴一下)。想着自己买了好多书，都闲在书架上，是不是也可以搞一个校园图书馆，把同学校的闲书都利用起来。然后又马上否定了自己，因为我自己其实并不愿意把自己买的书借给别人看。这个想法就此作罢。\n想想自己倒是有不少二手书带不走了，得卖掉，还有一些小物件，也带不走了，扔了还挺可惜，也可以卖掉。于是想找找哪里去买，学校 BBS 感觉还可以，但是信息没有分类，不易于检索，而且用户体验很不好。百姓、赶集、58 同城确实可以发布二手信息，但是信息又接近海量，不能针对目标人群——学生，比如我想找同专业学长学姐的书，那就不好找了，再比如我要找我考研学校的资料，那也不好找了，除非去相应的论坛，不然也是很难找。还有个大学生 C2C，这个网站感觉心太大，不靠谱，而且用户体验很差。而且到学生毕业的时候，将会有大量的二手信息，是不是可以考虑搭建一个大学生专用的二手物品交易平台，而且融入校园 SNS 和 LBS 元素，主打二手书籍的交易呢。综合考虑以上，我觉得这挺靠谱的。\n于是我打算开发一个这样的平台——摆摊网\n天大每年有 6000 多的毕业生，如果有 30%的毕业生能使用，那就将近 2000 人，融入 SNS 元素之后，用户会不会爆炸是增加？我觉得流量肯定会有的。\n于是立马动手，先是在美橙互联上注册了域名 baitan001.com (正宗的开心网也是 kaixin001.com )，然后找了几个同学听了他们意见，同时还邀请了两个同学一起来做这个小项目。\n感谢新浪 SAE 给了我实现自己的这个小小梦想的机会，前段时间参加新浪微博应用大赛，对 SAE 也有了一些了解，综合考虑之后，决定就在 SAE 平台上来做摆摊网的开发。\n花了昨天一晚上加上今天一下午，结合以往自己用过的 PHP 框架代码，而且动手折腾出了 Baitan 0.0.1 版本 PHP 框架，兼容 SAE 平台，接下来就是一步一步的把摆摊做出来了。\n这段时间一直忙于找工作，参加各种笔试面试，然后被各种鄙视，当然也收获了不少，短短不到一个月，也已经有了好几个巨头互联网公司的 offer 了，自己的工作也大概就这么定下来了。自己在大学和研究生阶段也折腾过不少东西了，但是还是没有做出一个让自己满意的东西，我想把摆摊网做好，即使没人用，我也自己去维护去完善，当做是自己在种地吧。我有梦想，但最开心的还是有个她愿意倾听我。\n今天把baitan001.com做了域名备案，申请了腾讯的企业邮箱，Baitan 0.0.1 框架的相关测试也已经基本完成，敬请期待摆摊网的到来吧！\n","permalink":"https://lewang.dev/posts/2011-09-28-hello-baigetan/","summary":"点评：留在这里，让自己可以看到以往无知的自己\n一转眼，20 年就过去了，终于要和自己 20 年的学生身份说拜拜了。\n那天去图书馆借书，回来的路上敬业湖桥边的那棵树的时候，心不由得感叹起来，这棵树，我已经拍下了它的春夏秋冬，它叶枯叶落，相似年年，只因它生根于此，我是要走的。\n去图书馆，边走边想，就想到了前几天在知乎上看到的社会化图书馆网站，感觉很不错(而且 UI 做的很不错，想借鉴一下)。想着自己买了好多书，都闲在书架上，是不是也可以搞一个校园图书馆，把同学校的闲书都利用起来。然后又马上否定了自己，因为我自己其实并不愿意把自己买的书借给别人看。这个想法就此作罢。\n想想自己倒是有不少二手书带不走了，得卖掉，还有一些小物件，也带不走了，扔了还挺可惜，也可以卖掉。于是想找找哪里去买，学校 BBS 感觉还可以，但是信息没有分类，不易于检索，而且用户体验很不好。百姓、赶集、58 同城确实可以发布二手信息，但是信息又接近海量，不能针对目标人群——学生，比如我想找同专业学长学姐的书，那就不好找了，再比如我要找我考研学校的资料，那也不好找了，除非去相应的论坛，不然也是很难找。还有个大学生 C2C，这个网站感觉心太大，不靠谱，而且用户体验很差。而且到学生毕业的时候，将会有大量的二手信息，是不是可以考虑搭建一个大学生专用的二手物品交易平台，而且融入校园 SNS 和 LBS 元素，主打二手书籍的交易呢。综合考虑以上，我觉得这挺靠谱的。\n于是我打算开发一个这样的平台——摆摊网\n天大每年有 6000 多的毕业生，如果有 30%的毕业生能使用，那就将近 2000 人，融入 SNS 元素之后，用户会不会爆炸是增加？我觉得流量肯定会有的。\n于是立马动手，先是在美橙互联上注册了域名 baitan001.com (正宗的开心网也是 kaixin001.com )，然后找了几个同学听了他们意见，同时还邀请了两个同学一起来做这个小项目。\n感谢新浪 SAE 给了我实现自己的这个小小梦想的机会，前段时间参加新浪微博应用大赛，对 SAE 也有了一些了解，综合考虑之后，决定就在 SAE 平台上来做摆摊网的开发。\n花了昨天一晚上加上今天一下午，结合以往自己用过的 PHP 框架代码，而且动手折腾出了 Baitan 0.0.1 版本 PHP 框架，兼容 SAE 平台，接下来就是一步一步的把摆摊做出来了。\n这段时间一直忙于找工作，参加各种笔试面试，然后被各种鄙视，当然也收获了不少，短短不到一个月，也已经有了好几个巨头互联网公司的 offer 了，自己的工作也大概就这么定下来了。自己在大学和研究生阶段也折腾过不少东西了，但是还是没有做出一个让自己满意的东西，我想把摆摊网做好，即使没人用，我也自己去维护去完善，当做是自己在种地吧。我有梦想，但最开心的还是有个她愿意倾听我。\n今天把baitan001.com做了域名备案，申请了腾讯的企业邮箱，Baitan 0.0.1 框架的相关测试也已经基本完成，敬请期待摆摊网的到来吧！","title":"摆摊网"},{"content":"本文为一个openlayers项目的总结，其中可能有一些遗漏或描述得不准确的地方，望批评指正。\n本文目标：\n构建一个完全自定义的轻量级地图信息系统，以openlayers为基础，需要自定义地图的元素样式，包括图层切换的样式、地图导航的样式、地图标记的实现、自定义弹出框等等。该专用地图信息系统地图来源于google地图，包括普通地图图层、卫星影像图层、路面信息图层，另外自行画了一些局部区域的三维地图，本项目为企业内部项目，与互联网隔离，故不可使用google的地图api，则需要抓取google的相关地图资源（暂时使用google地图资源），利用openlayers构建轻量级专用地图信息系统，这里的轻量级是指使用tilecache接口实现地图图层（相关资料见前篇博文）\n实现的效果图如下：\n图1 后台管理页面\n图2 前台展示页面（全屏显示）\n地图相关说明：\n地图页面事件说明\n地图基本功能\n(1) 左上角的地图浏览控件：包括地图的上下左右移动，地图的缩放\n(2) 右上角三个选项，包括地图、航拍、三维，点击每个选项，出现相应的地图视图，其中地图、航拍使用google地图数据（下载google地图）\n(3) 能够标注地图的行政区域\n(4) 鼠标在地图上双击鼠标左键，放大地图一个级别，鼠标滚轮向前向后转动分别是放大缩小地图一个级别，鼠标右键点击，无响应事件\n展示平台功能\n(1)主界面视图\na) 有若干个个图层，地图下面有各个图层的选项卡，点击每个选项，显示相应的内容\n(2)局部区域视图\na) 选中标记（点），跳转到局部区域视图\nb) 点击标记（点），根据项目的相应设置，显示地图下方的选项卡，点击选项卡，显示响应的内容\nc) 鼠标放在标记（点）上时。出现提示框，鼠标移到提示框上面，可以点击相应的链接，点击链接之后，弹出一个对话框，并且锁住地图界面\n地图数据说明\n地图基本数据\n(1)地图图片，要有连续的缩放级别的图，每个级别之间相差2倍\n(2)航拍图片，要有连续的缩放级别的图，每个级别之间相差2倍\n(3)三维图片，需要和地图图片、航拍图片保持一致的大小，和缩放级别和地理位置\n(4)地图效果图上面的使用的图标和弹出窗口图，需要原件，编写css需要使用\n","permalink":"https://lewang.dev/posts/2011-08-15-openlayers-custom/","summary":"本文为一个openlayers项目的总结，其中可能有一些遗漏或描述得不准确的地方，望批评指正。\n本文目标：\n构建一个完全自定义的轻量级地图信息系统，以openlayers为基础，需要自定义地图的元素样式，包括图层切换的样式、地图导航的样式、地图标记的实现、自定义弹出框等等。该专用地图信息系统地图来源于google地图，包括普通地图图层、卫星影像图层、路面信息图层，另外自行画了一些局部区域的三维地图，本项目为企业内部项目，与互联网隔离，故不可使用google的地图api，则需要抓取google的相关地图资源（暂时使用google地图资源），利用openlayers构建轻量级专用地图信息系统，这里的轻量级是指使用tilecache接口实现地图图层（相关资料见前篇博文）\n实现的效果图如下：\n图1 后台管理页面\n图2 前台展示页面（全屏显示）\n地图相关说明：\n地图页面事件说明\n地图基本功能\n(1) 左上角的地图浏览控件：包括地图的上下左右移动，地图的缩放\n(2) 右上角三个选项，包括地图、航拍、三维，点击每个选项，出现相应的地图视图，其中地图、航拍使用google地图数据（下载google地图）\n(3) 能够标注地图的行政区域\n(4) 鼠标在地图上双击鼠标左键，放大地图一个级别，鼠标滚轮向前向后转动分别是放大缩小地图一个级别，鼠标右键点击，无响应事件\n展示平台功能\n(1)主界面视图\na) 有若干个个图层，地图下面有各个图层的选项卡，点击每个选项，显示相应的内容\n(2)局部区域视图\na) 选中标记（点），跳转到局部区域视图\nb) 点击标记（点），根据项目的相应设置，显示地图下方的选项卡，点击选项卡，显示响应的内容\nc) 鼠标放在标记（点）上时。出现提示框，鼠标移到提示框上面，可以点击相应的链接，点击链接之后，弹出一个对话框，并且锁住地图界面\n地图数据说明\n地图基本数据\n(1)地图图片，要有连续的缩放级别的图，每个级别之间相差2倍\n(2)航拍图片，要有连续的缩放级别的图，每个级别之间相差2倍\n(3)三维图片，需要和地图图片、航拍图片保持一致的大小，和缩放级别和地理位置\n(4)地图效果图上面的使用的图标和弹出窗口图，需要原件，编写css需要使用","title":"改造openlayers构建轻量级专用地图信息系统（1）"},{"content":"为2011年mark一篇日志\n好久都没有做总结了，好久都没有做什么项目了，整天忙着上课，读论文，最近一段时间还开始忙着谈恋爱了。\n2011年开始这几个月读了数篇readings in database systems中超牛们的论文，对关系数据库知识也有了一定了解，而不是停留在以前仅仅会用关系数据库那个层次上了。还读了simpledb的源代码，minibase的源代码没有读完，也不想读了，很没意思，Jim Gray大神说关系数据库没啥搞头了，我觉得也是。Stonebraker大神也搞出了scidb，不搞传统的关系数据库了。\n好吧，我也不搞关系数据库了，开始跟进scidb吧。no-sql，not only-sql。我来了。\n","permalink":"https://lewang.dev/posts/2011-05-11-mark2011/","summary":"为2011年mark一篇日志\n好久都没有做总结了，好久都没有做什么项目了，整天忙着上课，读论文，最近一段时间还开始忙着谈恋爱了。\n2011年开始这几个月读了数篇readings in database systems中超牛们的论文，对关系数据库知识也有了一定了解，而不是停留在以前仅仅会用关系数据库那个层次上了。还读了simpledb的源代码，minibase的源代码没有读完，也不想读了，很没意思，Jim Gray大神说关系数据库没啥搞头了，我觉得也是。Stonebraker大神也搞出了scidb，不搞传统的关系数据库了。\n好吧，我也不搞关系数据库了，开始跟进scidb吧。no-sql，not only-sql。我来了。","title":"mark一下学习计划"},{"content":"最近一段时间里，又做了一个跟openlayers相关的项目，但是到目前为止，我对openlayers还是不怎么了解，做东西也只是参考了openlayers的例子，以及自己的一些对openlayers用法的一些猜测。openlayers是一个用js实现的GIS前端框架，我的js目前还是打酱油的水平，要是没有jquery，那就基本写不了几行js代码了。js那是相当的强大，再加上VML\\SVG、HTML5以及很多js rich框架等等，感觉js在浏览器端真是无所不能啊\n目前这个项目主要功能就是在某城市的地图上标示电缆、光缆、井盖、光缆接头、线杆、建筑物形状和位置、各分级单位的位置以及快速定位等等，要求的功能很简单，而且有前段时间做学校电子地图的一些积累，所以大概在两周左右就做完了这个项目。\n感觉openlayers的资料很少，自己也没有太多的时间和精力去读它的源码，目前我都是通过看openlayers自带的那些example和api文档来摸索的，下面简单总结一下openlayers的相关用法，欢迎大家拍砖。\n（以下均以OpenLayers-2.9.1为例说明，完整的项目代码在这里下载,地图图片http://121.193.130.68/list/）\n1、我只用到了openlayers里面的tilecache接口，其它的例如WMS，google map等等都没有用到。原因是这样的。tilecache接口是用来从tilecache服务器上请求图片的。通过观察google map或者一些其它的地图服务，发现他们都是使用256x256像素大小的图片（也就是tile）来拼接地图的，通常的做法是在gis服务器之间架设一个tilecache服务器，这样，gis生成的图片就可以缓存在tilecahe服务器上，而tilecache服务器仅仅提供简单http服务即可，如果请求的图片(tile)不在tilecache服务器上，那么tilecache服务器会想gis服务器请求，把把gis生成的图片缓存在tilecache服务器上。\n通过观察tilecache服务器的目录结构，发现缓存图片按照这样的模具结构存放：{缩放级别}/{横坐标}/{纵坐标}.{图片格式后缀名}，注意这个坐标系的原点在屏幕的左下角。观察到这个规律以后，就可以直接把一张大的地图按照256x256的大小切割，并按照上述的目录结构存放，值得注意的是，每个缩放级别要一定满足2^n的规律，即第一级整个地图大小若为1，那么第二级整个地图大小应为2，第三级应为4，依次类推。这里需要简单修改一下openlayers的tilecache接口代码，文件位于OpenLayers-2.9.1\\lib\\OpenLayers\\Layer\\TileCache.js,修改如下：\n1 var components = [\n2 this.layername,\n3 zeroPad(tileZ, 2),\n4 //zeroPad(parseInt(tileX / 1000000), 3),\n5 //zeroPad((parseInt(tileX / 1000) % 1000), 3),\n6 zeroPad((parseInt(tileX) % 1000), 3),\n7 //zeroPad(parseInt(tileY / 1000000), 3),\n8 //zeroPad((parseInt(tileY / 1000) % 1000), 3),\n9 zeroPad((parseInt(tileY) % 1000), 3) + \u0026lsquo;.\u0026rsquo; + this.extension\n10 ];\n同样，由于只使用tilecache，所以openlayers里面其它的代码也可删除，以减小openlayers的代码量，最后通过自带的python写的工具，压缩openlayers代码即可。\n2.地图初次加载的时候，向服务器请求所有数据信息，并在地图上绘出相应的点线面。如何画出建筑的轮廓？用透明的就行了。\na.显示点信息,包括画点、用图标显示点，代码如下。\n1 /*\n2 * 在地图上画出所有的点并保存点的相关信息到字典中\n3 */\n4 function initFeaturePoint(vectorLayer,categoryId){\n5 //get point json\n6 point_data = getFeatureData(\u0026lsquo;point\u0026rsquo;,categoryId);\n7 var target_info = [];\n8 // create a point feature\n9 for(index in point_data){\n10 single_point = point_data[index].point.split(\u0026quot; \u0026ldquo;);\n11 pointX = single_point[0];\n12 pointY = single_point[1];\n13\n14 //画点\n15 var point = new OpenLayers.Geometry.Point(Number(pointX), Number(pointY));\n16\n17\n18 var style_point = {\n19 strokeColor: point_data[index].strokeColor,\n20 strokeWidth: 2,\n21 strokeDashstyle: \u0026ldquo;solid\u0026rdquo;,\n22 pointRadius: 6,\n23 strokeOpacity: 0.8,\n24 fillOpacity: 0.8,\n25 //label:point_data[index].title,\n26 fontSize:\u0026lsquo;12px\u0026rsquo;,\n27 fontFamily:\u0026lsquo;宋体\u0026rsquo;,\n28 labelXOffset:0,\n29 labelYOffset:-15,\n30 labelAlign:\u0026rsquo;m\u0026rsquo;,\n31 fillColor: point_data[index].strokeColor\n32 };\n33\n34 //显示标记文字,把文字放在底下一层，这样上面的就不会选不上了\n35 var pointText = new OpenLayers.Geometry.Point(Number(pointX), Number(pointY));\n36 showPointTextTips(pointText, point_data[index].title);\n37\n38 pointFeature = new OpenLayers.Feature.Vector(point, null, style_point);\n39 vectorLayer.addFeatures([pointFeature]);\n40\n41 /收集点的信息/\n42 id = point_data[index].id;\n43 titles = point_data[index].title;\n44 description = point_data[index].description;\n45 strokeColor = point_data[index].strokeColor;\n46 iconUrl = point_data[index].iconUrl;\n47 iconUrl2 = point_data[index].iconUrl2;\n48 array_index = pointFeature.id;\n49 target_info[array_index] = {\u0026ldquo;id\u0026rdquo;:id,\u0026ldquo;title\u0026rdquo;:titles,\u0026ldquo;description\u0026rdquo;:description,\u0026ldquo;strokeColor\u0026rdquo;:strokeColor,\u0026ldquo;iconUrl\u0026rdquo;:iconUrl,\u0026ldquo;iconUrl2\u0026rdquo;:iconUrl2,\u0026ldquo;pointX\u0026rdquo;:pointX,\u0026ldquo;pointY\u0026rdquo;:pointY};\n50 //alert(target_info[array_index]);\n51 }\n52 return target_info;\n53 }\n1 /*\n2 * 在地图上画出所有的点并保存点的相关信息到字典中\n3 */\n4 function initFeatureImagePoint(vectorLayer,categoryId,imageUrl){\n5 //get point json\n6 point_data = getFeatureData(\u0026lsquo;point\u0026rsquo;,categoryId);\n7 var target_info = [];\n8 // create a point feature\n9 for(index in point_data){\n10 single_point = point_data[index].point.split(\u0026rdquo; \u0026ldquo;);\n11 pointX = single_point[0];\n12 pointY = single_point[1];\n13\n14 //画点\n15 var point = new OpenLayers.Geometry.Point(Number(pointX), Number(pointY));\n16\n17\n18 var style_point = {\n19 graphicWidth : 32,\n20 graphicHeight: 32,\n21 externalGraphic:imageUrl,\n22 graphicTitle:point_data[index].title\n23 };\n24\n25\n26 pointFeature = new OpenLayers.Feature.Vector(point, null, style_point);\n27 vectorLayer.addFeatures([pointFeature]);\n28\n29 /收集点的信息/\n30 id = point_data[index].id;\n31 titles = point_data[index].title;\n32 description = point_data[index].description;\n33 strokeColor = point_data[index].strokeColor;\n34 iconUrl = point_data[index].iconUrl;\n35 iconUrl2 = point_data[index].iconUrl2;\n36 array_index = pointFeature.id;\n37 target_info[array_index] = {\u0026ldquo;id\u0026rdquo;:id,\u0026ldquo;title\u0026rdquo;:titles,\u0026ldquo;description\u0026rdquo;:description,\u0026ldquo;strokeColor\u0026rdquo;:strokeColor,\u0026ldquo;iconUrl\u0026rdquo;:iconUrl,\u0026ldquo;iconUrl2\u0026rdquo;:iconUrl2,\u0026ldquo;pointX\u0026rdquo;:pointX,\u0026ldquo;pointY\u0026rdquo;:pointY};\n38 //alert(target_info[array_index]);\n39 }\n40 return target_info;\n41 }\nb.画线\n1 /**\n2 在地图上画出所有的线并保存点的相关信息到字典中\n3 */\n4 function initFeatureLine(vectorLayer,categoryId)\n5 {\n6\n7 linestring_data = getFeatureData(\u0026rsquo;line\u0026rsquo;,categoryId);\n8 var target_info = [];\n9 // create all line features from a list of points\n10 for(index in linestring_data){\n11 var pointList = [];\n12 linestring_points = linestring_data[index].point.split(\u0026rdquo;,\u0026quot;);\n13 for(inner_index in linestring_points){\n14 single_point = linestring_points[inner_index].split(\u0026quot; \u0026ldquo;);\n15 pointX = single_point[0];\n16 pointY = single_point[1];\n17 newPoint = new OpenLayers.Geometry.Point(Number(pointX), Number(pointY));\n18 pointList.push(newPoint);\n19 }\n20 var linestring = new OpenLayers.Geometry.LineString(pointList);\n21 var style_line = {\n22 strokeColor: linestring_data[index].strokeColor,\n23 strokeOpacity: 0.8,\n24 strokeWidth: linestring_data[index].strokeWidth,\n25 pointRadius: 20,\n26 //label:linestring_data[index].title,\n27 fontSize:\u0026lsquo;12px\u0026rsquo;,\n28 fontFamily:\u0026lsquo;宋体\u0026rsquo;,\n29 labelXOffset:30,\n30 labelYOffset:10,\n31 labelAlign:\u0026lsquo;rm\u0026rsquo;\n32 };\n33 lineFeature = new OpenLayers.Feature.Vector(linestring,null,style_line);\n34 vectorLayer.addFeatures([lineFeature]);\n35\n36 //显示标记文字\n37 single_point = linestring_points[0].split(\u0026rdquo; \u0026ldquo;);\n38 pointX = single_point[0];\n39 pointY = single_point[1];\n40 var pointText = new OpenLayers.Geometry.Point(Number(pointX), Number(pointY));\n41 showPointTextTips(pointText, linestring_data[index].title);\n42\n43 /收集路的信息/\n44 id = linestring_data[index].id;\n45 titles = linestring_data[index].title;\n46 linelength = linestring_data[index].linelength;\n47 description = linestring_data[index].description;\n48 strokeColor = linestring_data[index].strokeColor;\n49 strokeWidth = linestring_data[index].strokeWidth;\n50 iconUrl = linestring_data[index].iconUrl;\n51 iconUrl2 = linestring_data[index].iconUrl2;\n52 array_index = linestring.id;\n53 target_info[array_index] = {\u0026ldquo;id\u0026rdquo;:id,\u0026ldquo;title\u0026rdquo;:titles,\u0026ldquo;linelength\u0026rdquo;:linelength,\u0026ldquo;description\u0026rdquo;:description,\u0026ldquo;strokeColor\u0026rdquo;:strokeColor,\u0026ldquo;strokeWidth\u0026rdquo;:strokeWidth,\u0026ldquo;iconUrl\u0026rdquo;:iconUrl,\u0026ldquo;iconUrl2\u0026rdquo;:iconUrl2};\n54 }\n55 return target_info;\n56 }\nc.画面\n1 /**\n2 在地图上画出所有的多边形区域路并保存点的相关信息到字典中\n3 */\n4 function initFeaturePolygon(vectorLayer,categoryId)\n5 {\n6 polygon_data = getFeatureData(\u0026lsquo;polygon\u0026rsquo;,categoryId);\n7 var target_info = [];\n8 for(index in polygon_data){\n9 var pointList = [];\n10 polygon_points = polygon_data[index].point.split(\u0026rdquo;,\u0026quot;);\n11 for(inner_index in polygon_points){\n12 single_point = polygon_points[inner_index].split(\u0026quot; \u0026ldquo;);\n13 pointX = single_point[0];\n14 pointY = single_point[1];\n15 newPoint = new OpenLayers.Geometry.Point(Number(pointX), Number(pointY));\n16 pointList.push(newPoint);\n17 }\n18 var linearRing = new OpenLayers.Geometry.LinearRing(pointList);\n19 var polygon = new OpenLayers.Geometry.Polygon([linearRing]);\n20 var style_polygon = {\n21 strokeColor: polygon_data[index].strokeColor,\n22 strokeWidth: 2,\n23 strokeOpacity: 0.8,\n24 fillOpacity: 0.8,\n25 //label:polygon_data[index].title,\n26 fontSize:\u0026lsquo;12px\u0026rsquo;,\n27 fontFamily:\u0026lsquo;宋体\u0026rsquo;,\n28 labelXOffset:0,\n29 labelYOffset:-15,\n30 labelAlign:\u0026rsquo;m\u0026rsquo;,\n31 fillColor: polygon_data[index].strokeColor\n32 };\n33 polygonFeature = new OpenLayers.Feature.Vector(polygon,null,style_polygon);\n34 vectorLayer.addFeatures([polygonFeature]);\n35\n36\n37 //显示标记文字\n38 single_point = polygon_points[0].split(\u0026rdquo; \u0026ldquo;);\n39 pointX = single_point[0];\n40 pointY = single_point[1];\n41 var pointText = new OpenLayers.Geometry.Point(Number(pointX), Number(pointY));\n42 showPointTextTips(pointText, polygon_data[index].title);\n43\n44 /收集图形的信息/\n45 id = polygon_data[index].id;\n46 titles = polygon_data[index].title;\n47 description = polygon_data[index].description;\n48 type_id = polygon_data[index].type_id;\n49 strokeColor = polygon_data[index].strokeColor;\n50 iconUrl = polygon_data[index].iconUrl;\n51 iconUrl2 = polygon_data[index].iconUrl2;\n52 array_index = polygon.id;\n53 //alert(array_index);\n54 target_info[array_index] = {\u0026ldquo;id\u0026rdquo;:id,\u0026ldquo;title\u0026rdquo;:titles,\u0026ldquo;description\u0026rdquo;:description,\u0026ldquo;type_id\u0026rdquo;:type_id,\u0026ldquo;strokeColor\u0026rdquo;:strokeColor,\u0026ldquo;iconUrl\u0026rdquo;:iconUrl,\u0026ldquo;iconUrl2\u0026rdquo;:iconUrl2};\n55 }\n56 return target_info;\n57 }\nd.复制一条线\n1 /**\n2 *\n3 * 复制线到某一层\n4 * @param desvectors\n5 * @param linestring\n6 * @return\n7 */\n8 function cloneLine(vectorLayer,linestring)\n9 {\n10 linestring_points = linestring.substring(\u0026ldquo;LINESTRING\u0026rdquo;.length + 1,linestring.length - 1);\n11 //alert(linestring_points);\n12 var pointList = [];\n13 line_points = linestring_points.split(\u0026rdquo;,\u0026quot;);\n14 for(inner_index in line_points){\n15 single_point = line_points[inner_index].split(\u0026quot; \u0026ldquo;);\n16 pointX = single_point[0];\n17 pointY = single_point[1];\n18 newPoint = new OpenLayers.Geometry.Point(Number(pointX)+0.2, Number(pointY)+0.2);\n19 pointList.push(newPoint);\n20 }\n21 var linestringtmp = new OpenLayers.Geometry.LineString(pointList);\n22 var style_line = {\n23 strokeColor: \u0026ldquo;red\u0026rdquo;,\n24 strokeOpacity: 1,\n25 strokeWidth: 6,\n26 pointRadius: 20\n27 };\n28 //var tmpVector = vectorLayer == \u0026rsquo;lightlinelayer\u0026rsquo; ? lightlinelayer : linelayer;\n29 var tmpVector;\n30 if(vectorLayer == \u0026rsquo;lightlinelayer\u0026rsquo;)\n31 tmpVector = lightlinelayer;\n32 else if(vectorLayer == \u0026rsquo;linelayer\u0026rsquo;)\n33 tmpVector = linelayer;\n34 else\n35 tmpVector = policelightlinelayer;\n36\n37 lineFeature = new OpenLayers.Feature.Vector(linestringtmp,null,style_line);\n38 tmpVector.addFeatures([lineFeature]);\n39 }\ne.计算一条线的长度\n1 /计算线的长度/\n2 function getLengthOfALine(linestring)\n3 {\n4 //alert(linestring);\n5 linestring_points = linestring.substring(\u0026ldquo;LINESTRING\u0026rdquo;.length + 1,linestring.length - 1);\n6 //alert(linestring_points);\n7 var pointList = [];\n8 line_points = linestring_points.split(\u0026rdquo;,\u0026quot;);\n9 lineLength = 0;\n10 for(inner_index in line_points){\n11 if(inner_index \u0026gt; 0)\n12 {\n13 single_point = line_points[inner_index].split(\u0026quot; \u0026ldquo;);\n14 pointX = Number(single_point[0]);\n15 pointY = Number(single_point[1]);\n16\n17 old_single_point = line_points[inner_index - 1].split(\u0026rdquo; \u0026ldquo;);\n18 old_pointX = Number(old_single_point[0]);\n19 old_pointY = Number(old_single_point[1]);\n20\n21 lineLength = lineLength + Math.sqrt((pointX - old_pointX) * (pointX - old_pointX) + (pointY - old_pointY) * (pointY - old_pointY));\n22 }\n23 }\n24 return lineLength.toFixed(4);\n25 }\nf.删除一个地图元素\n1 function deleteFeature(featureId)\n2 {\n3\n4 if(!confirm(\u0026ldquo;您确定要删除这个地图标记吗？\u0026rdquo;))\n5 return;\n6 var feature = null;\n7 //alert(lightlinelayer.getFeatureById(featureId));\n8 feature = lightlinelayer.getFeatureById(featureId);\n9 //alert(feature);\n10 if(feature != null)\n11 {\n12 if(lineDataList[feature.geometry.id])\n13 {\n14 deleteFeatureByIdAndType(lineDataList[feature.geometry.id].id,\u0026rsquo;line\u0026rsquo;);\n15 }\n16 map.removePopup(feature.popup);\n17 feature.popup.destroy();\n18 feature.popup = null;\n19 lightlinelayer.destroyFeatures([feature]);\n20 return;\n21 }\n地图图片是从google map下载的。\n","permalink":"https://lewang.dev/posts/2010-10-11-how_to_do_with_openlayers/","summary":"最近一段时间里，又做了一个跟openlayers相关的项目，但是到目前为止，我对openlayers还是不怎么了解，做东西也只是参考了openlayers的例子，以及自己的一些对openlayers用法的一些猜测。openlayers是一个用js实现的GIS前端框架，我的js目前还是打酱油的水平，要是没有jquery，那就基本写不了几行js代码了。js那是相当的强大，再加上VML\\SVG、HTML5以及很多js rich框架等等，感觉js在浏览器端真是无所不能啊\n目前这个项目主要功能就是在某城市的地图上标示电缆、光缆、井盖、光缆接头、线杆、建筑物形状和位置、各分级单位的位置以及快速定位等等，要求的功能很简单，而且有前段时间做学校电子地图的一些积累，所以大概在两周左右就做完了这个项目。\n感觉openlayers的资料很少，自己也没有太多的时间和精力去读它的源码，目前我都是通过看openlayers自带的那些example和api文档来摸索的，下面简单总结一下openlayers的相关用法，欢迎大家拍砖。\n（以下均以OpenLayers-2.9.1为例说明，完整的项目代码在这里下载,地图图片http://121.193.130.68/list/）\n1、我只用到了openlayers里面的tilecache接口，其它的例如WMS，google map等等都没有用到。原因是这样的。tilecache接口是用来从tilecache服务器上请求图片的。通过观察google map或者一些其它的地图服务，发现他们都是使用256x256像素大小的图片（也就是tile）来拼接地图的，通常的做法是在gis服务器之间架设一个tilecache服务器，这样，gis生成的图片就可以缓存在tilecahe服务器上，而tilecache服务器仅仅提供简单http服务即可，如果请求的图片(tile)不在tilecache服务器上，那么tilecache服务器会想gis服务器请求，把把gis生成的图片缓存在tilecache服务器上。\n通过观察tilecache服务器的目录结构，发现缓存图片按照这样的模具结构存放：{缩放级别}/{横坐标}/{纵坐标}.{图片格式后缀名}，注意这个坐标系的原点在屏幕的左下角。观察到这个规律以后，就可以直接把一张大的地图按照256x256的大小切割，并按照上述的目录结构存放，值得注意的是，每个缩放级别要一定满足2^n的规律，即第一级整个地图大小若为1，那么第二级整个地图大小应为2，第三级应为4，依次类推。这里需要简单修改一下openlayers的tilecache接口代码，文件位于OpenLayers-2.9.1\\lib\\OpenLayers\\Layer\\TileCache.js,修改如下：\n1 var components = [\n2 this.layername,\n3 zeroPad(tileZ, 2),\n4 //zeroPad(parseInt(tileX / 1000000), 3),\n5 //zeroPad((parseInt(tileX / 1000) % 1000), 3),\n6 zeroPad((parseInt(tileX) % 1000), 3),\n7 //zeroPad(parseInt(tileY / 1000000), 3),\n8 //zeroPad((parseInt(tileY / 1000) % 1000), 3),\n9 zeroPad((parseInt(tileY) % 1000), 3) + \u0026lsquo;.\u0026rsquo; + this.extension\n10 ];\n同样，由于只使用tilecache，所以openlayers里面其它的代码也可删除，以减小openlayers的代码量，最后通过自带的python写的工具，压缩openlayers代码即可。\n2.地图初次加载的时候，向服务器请求所有数据信息，并在地图上绘出相应的点线面。如何画出建筑的轮廓？用透明的就行了。\na.显示点信息,包括画点、用图标显示点，代码如下。\n1 /*\n2 * 在地图上画出所有的点并保存点的相关信息到字典中\n3 */\n4 function initFeaturePoint(vectorLayer,categoryId){","title":"openlayers小结(有参考项目代码)"},{"content":"好久没有在blogjava上面写东西了，前段时间一直在忙两个项目，忙到没有时间做一些总结，还好项目进展都还不错。这段时间自己虽然搞得很忙，但是自己却感到非常的开心，我突然觉得这个世界好像都是属于我的：遇到了很多给我帮助的人，做了很多自己喜欢做的事，开始了自己特别向往的生活。\n突然觉得自己的梦想正在慢慢靠近，简直是触手可及，但是我还是冷静了一下，没有急忙着伸出手，还是把梦想放在高高的地方，让自己一直都能看到，然后去追逐吧。\n觉得自己越来越感性，喜欢研究血型，喜欢研究星座，喜欢思考，喜欢去捕捉自己的点点思绪，然后写成文字，留在纸上，也觉得自己越来越走向偏执，而不是小时候那种的任性。\n言归正传，说说自己参加的这次盛大校园牛人创新技术大赛。比赛相关细节请访问http://ic.sdo.com/。这是自己再次成为学生以后参加的第一个面向全国在校学生的技术大赛，在这个比赛中，我一个人一组，并最终进入决赛获得了三等奖，拿到了1w块的奖金。自己真是太幸运了，从决定参加比赛到完成代码大概用了两个星期的时间，而且主要的编码工作还只是用了两个周末的时间来完成的。\n下面说说我做了什么。\n我很喜欢旅游，喜欢背着包安静的行走，拍下自己路过的一切景色，也很喜欢在自己的日记本里记下自己每一天的心情。这个小应用旨在让每一个喜欢旅游的中国人都能在自己祖国的版图上留下自己的足迹，形成自己的足迹快照，并且能够在自己留下的足迹的地方添加日志和上传照片，这样去过相同地方的人就可以交流着相互的旅行记忆，想去同一个地方的人也可以组团或者提供那种类似“沙发客”的那种服务，帮助别人实现自己的旅行梦想。同时，可以对用户的数据进行发掘，也许20年后，在相同一天去过某个地方的人会收到一封相同的信，内容是我们为他保留的日志和照片；足迹中国还应该积极发掘想要去相同地方的或者路线相同相似的人，推荐他们组成一个旅行梦之队，为这些人提供订票、预定住所、购买保险等等一站式的旅游服务。\n我给这个小应用取名为足迹中国。\n因为我想：\n在自己去过的每一个地方留下足迹，分享自己的旅行记忆\n在自己想去的每一个地方做个标记，分享自己的旅行梦想。\n东西弄得很简单，技术细节就不说了。下面说说比赛入围以后的，我去到盛大的一些所见所闻。\n说到比赛，我想除了收获了最直接的奖金之外，还有如朋友、实实在在的企业体验、以及对盛大的了解等等。我想，说到盛大，几乎在每一个中国网民的心里网络游戏简直就是他的代名词，在参观了盛大创新院、盛大在线、盛大客服之后以及HR老师们培训的过程中，HR老师不断问我们盛大是一个什么类型的企业，最后终于被成功洗脑，我现在可以清楚的说出盛大是一个互动娱乐媒体企业了。\n在上海待的这几天，盛大在线和盛大创新院的老师们都给我们做了比较周到的安排。第一天上午创新院的范老师和庄老师带领我们参观了创新院(其实之前我已经来过创新院了，并且待了一个多月)，并给我们下午的演讲做了一次演练。下午的时候盛大在线的朱总、创新院的郭院长以及其他几位盛大各部门的大牛们准时坐到了评委席，我第一个上，有点紧张啊(此处省去200字)。晚上的时候和专家们共进晚餐，可以和专家直接交谈，咨询技术问题，交流自己的想法确实是一件非常爽的事情。当晚还和HR的老师们交流和很长时间，了解到了盛大的一些福利待遇，最重要的是HR姐姐们传授了我们很多找工作方面的有用经验。当然HR姐姐们还说那张获奖证书就是盛大的绿卡。。。第一天就这样过去了。\n第二天参观了盛大在线和客服中心，并且最终揭晓了奖项情况。最后很意外的让我代表了所有获奖同学上台做个简单的发言。真是有点激动啊，被代表了20多年了，今天终于可以代表一下别人了。立马沉住气，开始打腹稿。等到走上讲台之后，才发现刚才打完的腹稿早就被胃消化，没被吸收就直接进入大肠了。算了，于是胡言乱语了一通，现在只记得说自己想去创新院实习。因为自己确实希望能去创新院实习。我在创新院呆过一段时间，对那里的技术氛围有些了解，感觉那里的每个人都超牛，每个人都能不断的闪出非常吸引人的点子。那里环境优雅，一切都非常好，给人一种自由的感觉。\nft。。。一下子写了这么多了。。。就此搁笔，期待奖金早日入账！我就可以换个android的手机了\n","permalink":"https://lewang.dev/posts/2010-09-18-snda/","summary":"好久没有在blogjava上面写东西了，前段时间一直在忙两个项目，忙到没有时间做一些总结，还好项目进展都还不错。这段时间自己虽然搞得很忙，但是自己却感到非常的开心，我突然觉得这个世界好像都是属于我的：遇到了很多给我帮助的人，做了很多自己喜欢做的事，开始了自己特别向往的生活。\n突然觉得自己的梦想正在慢慢靠近，简直是触手可及，但是我还是冷静了一下，没有急忙着伸出手，还是把梦想放在高高的地方，让自己一直都能看到，然后去追逐吧。\n觉得自己越来越感性，喜欢研究血型，喜欢研究星座，喜欢思考，喜欢去捕捉自己的点点思绪，然后写成文字，留在纸上，也觉得自己越来越走向偏执，而不是小时候那种的任性。\n言归正传，说说自己参加的这次盛大校园牛人创新技术大赛。比赛相关细节请访问http://ic.sdo.com/。这是自己再次成为学生以后参加的第一个面向全国在校学生的技术大赛，在这个比赛中，我一个人一组，并最终进入决赛获得了三等奖，拿到了1w块的奖金。自己真是太幸运了，从决定参加比赛到完成代码大概用了两个星期的时间，而且主要的编码工作还只是用了两个周末的时间来完成的。\n下面说说我做了什么。\n我很喜欢旅游，喜欢背着包安静的行走，拍下自己路过的一切景色，也很喜欢在自己的日记本里记下自己每一天的心情。这个小应用旨在让每一个喜欢旅游的中国人都能在自己祖国的版图上留下自己的足迹，形成自己的足迹快照，并且能够在自己留下的足迹的地方添加日志和上传照片，这样去过相同地方的人就可以交流着相互的旅行记忆，想去同一个地方的人也可以组团或者提供那种类似“沙发客”的那种服务，帮助别人实现自己的旅行梦想。同时，可以对用户的数据进行发掘，也许20年后，在相同一天去过某个地方的人会收到一封相同的信，内容是我们为他保留的日志和照片；足迹中国还应该积极发掘想要去相同地方的或者路线相同相似的人，推荐他们组成一个旅行梦之队，为这些人提供订票、预定住所、购买保险等等一站式的旅游服务。\n我给这个小应用取名为足迹中国。\n因为我想：\n在自己去过的每一个地方留下足迹，分享自己的旅行记忆\n在自己想去的每一个地方做个标记，分享自己的旅行梦想。\n东西弄得很简单，技术细节就不说了。下面说说比赛入围以后的，我去到盛大的一些所见所闻。\n说到比赛，我想除了收获了最直接的奖金之外，还有如朋友、实实在在的企业体验、以及对盛大的了解等等。我想，说到盛大，几乎在每一个中国网民的心里网络游戏简直就是他的代名词，在参观了盛大创新院、盛大在线、盛大客服之后以及HR老师们培训的过程中，HR老师不断问我们盛大是一个什么类型的企业，最后终于被成功洗脑，我现在可以清楚的说出盛大是一个互动娱乐媒体企业了。\n在上海待的这几天，盛大在线和盛大创新院的老师们都给我们做了比较周到的安排。第一天上午创新院的范老师和庄老师带领我们参观了创新院(其实之前我已经来过创新院了，并且待了一个多月)，并给我们下午的演讲做了一次演练。下午的时候盛大在线的朱总、创新院的郭院长以及其他几位盛大各部门的大牛们准时坐到了评委席，我第一个上，有点紧张啊(此处省去200字)。晚上的时候和专家们共进晚餐，可以和专家直接交谈，咨询技术问题，交流自己的想法确实是一件非常爽的事情。当晚还和HR的老师们交流和很长时间，了解到了盛大的一些福利待遇，最重要的是HR姐姐们传授了我们很多找工作方面的有用经验。当然HR姐姐们还说那张获奖证书就是盛大的绿卡。。。第一天就这样过去了。\n第二天参观了盛大在线和客服中心，并且最终揭晓了奖项情况。最后很意外的让我代表了所有获奖同学上台做个简单的发言。真是有点激动啊，被代表了20多年了，今天终于可以代表一下别人了。立马沉住气，开始打腹稿。等到走上讲台之后，才发现刚才打完的腹稿早就被胃消化，没被吸收就直接进入大肠了。算了，于是胡言乱语了一通，现在只记得说自己想去创新院实习。因为自己确实希望能去创新院实习。我在创新院呆过一段时间，对那里的技术氛围有些了解，感觉那里的每个人都超牛，每个人都能不断的闪出非常吸引人的点子。那里环境优雅，一切都非常好，给人一种自由的感觉。\nft。。。一下子写了这么多了。。。就此搁笔，期待奖金早日入账！我就可以换个android的手机了","title":"小记盛大校园牛人创新技术大赛"},{"content":"2010年的第一篇日志献给了firefox\n原因很简单，我要删掉删除Firefox地址栏下拉列表中指定的几条记录，试了del、alt+del、shift+del、ctrl+del都不管用\n才发现这些记录都已经是书签了，于是选择:书签\u0026ndash;\u0026gt;管理书签\u0026ndash;\u0026gt;在搜索书签栏里面输入要删除的网址，找到删除即可\n","permalink":"https://lewang.dev/posts/2010-01-11-del-firefox-address/","summary":"2010年的第一篇日志献给了firefox\n原因很简单，我要删掉删除Firefox地址栏下拉列表中指定的几条记录，试了del、alt+del、shift+del、ctrl+del都不管用\n才发现这些记录都已经是书签了，于是选择:书签\u0026ndash;\u0026gt;管理书签\u0026ndash;\u0026gt;在搜索书签栏里面输入要删除的网址，找到删除即可","title":"删除Firefox地址栏下拉列表中的指定记录"},{"content":"什么是libfetion？请访问http://www.libfetion.cn/查看\n以下都是在ubuntu下进行的\n参考\nhttp://www.libfetion.cn/Docs-dve/Build-LibFx-on-ubuntu.txt\n请使用svn客户端下载libfetion-gui的源码\nhttp://libfetion-gui.googlecode.com/svn/\n安装开发过程中需要的软件\n1 sudo apt-get install libc-dev\n2 sudo apt-get install g++\n3 sudo apt-get install libcurl4-openssl-dev\n在trunk/qt4_src/libfetion/lib目录下找到静态库libfetion_32.a，由于代码打算是用python来写，所以制作了一个动态库libfetion.so\n具体步骤如下：\n#解压静态库为*.o\nar -x libfetion_32.a\n#重新封装为so\ng++ -shared -Wall -fPIC -lcurl -pthread *.o -o libfetion.so\n#查看so[可选步骤]\nnm libfetion.so\n发送天气预报的python代码如下，libfetion.so中具体的方法请查看trunk/qt4_src/libfetion/include/libfetion/libfetion.h\n1 #!/usr/bin/env python\n2 #coding=utf-8\n3 #only can run at linux\n4 import os\n5 import ctypes\n6 import urllib2\n7\n8 #城市代码列表\n9 city_codes = {\n10 \u0026lsquo;合肥\u0026rsquo; : \u0026lsquo;CHXX0448\u0026rsquo;,\n11 \u0026lsquo;安庆\u0026rsquo; : \u0026lsquo;CHXX0452\u0026rsquo;,\n12 \u0026lsquo;天津\u0026rsquo; : \u0026lsquo;CHXX0133\u0026rsquo;,\n13 \u0026lsquo;南昌\u0026rsquo; : \u0026lsquo;CHXX0097\u0026rsquo;,\n14 \u0026lsquo;上海\u0026rsquo; : \u0026lsquo;CHXX0097\u0026rsquo;,\n15 \u0026lsquo;北京\u0026rsquo; : \u0026lsquo;CHXX0097\u0026rsquo;,\n16 \u0026lsquo;长沙\u0026rsquo; : \u0026lsquo;CHXX0013\u0026rsquo;,\n17 \u0026lsquo;常德\u0026rsquo; : \u0026lsquo;CHXX0416\u0026rsquo;,\n18 \u0026lsquo;北京\u0026rsquo; : \u0026lsquo;CHXX0008\u0026rsquo;,\n19 \u0026lsquo;银川\u0026rsquo; : \u0026lsquo;CHXX0259\u0026rsquo;\n20 }\n21\n22 #用户定制城市\n23 weather_users = {\n24 \u0026lsquo;天津\u0026rsquo;:[\u0026lsquo;1383\u0026rsquo;,\u0026lsquo;1597\u0026rsquo;,\u0026lsquo;1592\u0026rsquo;,\u0026lsquo;1506\u0026rsquo;,\u0026lsquo;1351\u0026rsquo;],\n25 \u0026lsquo;北京\u0026rsquo;:[\u0026lsquo;1592\u0026rsquo;],\n26 \u0026lsquo;银川\u0026rsquo;:[\u0026lsquo;1592\u0026rsquo;]\n27 }\n28\n29 #自己手机\n30 myself_city_list = [\u0026lsquo;天津\u0026rsquo;,\u0026lsquo;安庆\u0026rsquo;]\n31\n32 class weather:\n33 weatherBaseUrl = \u0026ldquo;http://www.thinkpage.cn/weather/weather.aspx?uid=\u0026amp;l=zh- CN\u0026amp;p=CMA\u0026amp;a=0\u0026amp;u=C\u0026amp;s=4\u0026amp;m=0\u0026amp;x=1\u0026amp;d=2\u0026amp;fc=\u0026amp;bgc=\u0026amp;bc=\u0026amp;ti=1\u0026amp;in=1\u0026amp;li=2\u0026amp;c=\u0026rdquo;\n34 #初始化\n35 def init(self):\n36 pass\n37\n38 #清空html\n39 def clear_html(self):\n40 cmd = \u0026lsquo;rm -f .htm\u0026rsquo;\n41 os.popen(cmd)\n42\n43 #获得需要发送的城市代码\n44 def __getCityToSend(self):\n45 self.city_all = {}\n46 for key in weather_users.keys():\n47 self.city_all[key] = 1\n48 for key in myself_city_list:\n49 self.city_all[key] = 1\n50\n51 #获取html\n52 def get_html(self):\n53 self.__getCityToSend();\n54 for key in self.city_all.keys():\n55 weatherUrl = self.weatherBaseUrl + city_codes[key]\n56 req = urllib2.Request(weatherUrl)\n57 res = urllib2.urlopen(req)\n58 weather_content = res.read()\n59 res.close()\n60 file_html = open(city_codes[key] + \u0026lsquo;.htm\u0026rsquo;,\u0026lsquo;w\u0026rsquo;)\n61 file_html.write(weather_content)\n62 file_html.close()\n63\n64 #过滤无用信息\n65 def parse_html(self):\n66 for key in self.city_all.keys():\n67 file_name = city_codes[key] + \u0026lsquo;.htm\u0026rsquo;\n68\n69 #获得信息所在行\n70 cmd = \u0026lsquo;cat %s.htm |grep -E 'ltl|forecastDay|temp'|grep -v spanDate \u0026gt; %s.htm\u0026rsquo; % (city_codes[key],city_codes[key])\n71 os.popen(cmd)\n72\n73 #去掉html代码\n74 cmd = \u0026lsquo;sed -i -e 's/\u0026lt;[^\u0026gt;]\u0026gt;//g' %s.htm\u0026rsquo; % city_codes[key]\n75 os.popen(cmd)\n76\n77 #去掉不相关字符\n78 cmd = \u0026lsquo;sed -i -e 's/°/°/g;s/ //g' %s.htm\u0026rsquo; % city_codes[key]\n79 os.popen(cmd)\n80\n81 #根据城市生成消息\n82 def __generate_msg(self,city_key):\n83 #打开文件\n84 file_html = open(city_codes[city_key] + \u0026lsquo;.htm\u0026rsquo;)\n85\n86 #读取信息\n87 weather_lines = file_html.readlines()\n88 file_html.close()\n89 weather_content = \u0026rsquo;\u0026rsquo;\n90 for line in weather_lines:\n91 weather_content = weather_content + line.replace(\u0026rsquo;\\r\\n\u0026rsquo;,\u0026rsquo; \u0026lsquo;)\n92 return weather_content\n93\n94 #登录飞信\n95 def fetion_login(self, your_mobile_no, your_pwd):\n96 self.libc = ctypes.cdll.LoadLibrary(\u0026rsquo;/home/loh/weather/libfetion.so\u0026rsquo;)\n97 self.libc.fx_init()\n98 self.libc.fs_login(your_mobile_no,your_pwd)\n99 self.libc.fx_set_longsms(True)\n100\n101 #退出飞信\n102 def fetion_logout(self):\n103 self.libc.fx_loginout()\n104 self.libc.fx_terminate()\n105\n106 #给自己发信息\n107 def send_msg_to_myself(self):\n108 for city_key in myself_city_list:\n109 msg = self.__generate_msg(city_key)\n110 self.libc.fs_send_sms_to_self(msg)\n111\n112 #给用户发信息\n113 def send_msg(self):\n114 for city_key in weather_users.keys():\n115 msg = self.__generate_msg(city_key)\n116 for user in weather_users[city_key]:\n117 self.libc.fs_send_sms_by_mobile_no(user,msg)\n118\n119 #给用户发送欢迎信息\n120 def send_welcome_msg(self, msg):\n121 user_all = {}\n122 for user_list in weather_users.values():\n123 for user in user_list:\n124 user_all[user] = 1\n125\n126 for user in user_all.keys():\n127 self.libc.fs_send_sms_by_mobile_no(user,msg)\n128\n129\n130 if name == \u0026lsquo;main\u0026rsquo;:\n131 weather = weather()\n132 weather.get_html()\n133 weather.parse_html()\n134 weather.fetion_login(\u0026lsquo;1363\u0026rsquo;, \u0026lsquo;password\u0026rsquo;)\n135 weather.send_msg_to_myself()\n136 weather.send_msg()\n137 #weather.send_welcome_msg(\u0026lsquo;您好！天气预报全线升级，支持国内所有城市以及国外主要大城市，此外您还可以定制多个城市\u0026rsquo;)\n138 weather.fetion_logout()\n139 weather.clear_html()\n编写脚本和任务计划\n编写脚本：\nvim weather\n写入以下内容\n1 cd /home/loh/weather#你的脚本所在目录\n2 python weather.py\nchmod +x weather\n任务计划：\ncrontab -e\n写入以下内容（每天早上7点1分执行脚本）\n1 # m h dom mon dow command\n2 1 7 * * * /home/loh/weather/weather\n天气预报内容是这样的：\n天津 阴 9.3°C 感觉8°C 风力南1级 湿度60% 今天晴转雾 9/2°C 明天雾转多云 9/0°C\n","permalink":"https://lewang.dev/posts/2009-11-23-libfetion/","summary":"什么是libfetion？请访问http://www.libfetion.cn/查看\n以下都是在ubuntu下进行的\n参考\nhttp://www.libfetion.cn/Docs-dve/Build-LibFx-on-ubuntu.txt\n请使用svn客户端下载libfetion-gui的源码\nhttp://libfetion-gui.googlecode.com/svn/\n安装开发过程中需要的软件\n1 sudo apt-get install libc-dev\n2 sudo apt-get install g++\n3 sudo apt-get install libcurl4-openssl-dev\n在trunk/qt4_src/libfetion/lib目录下找到静态库libfetion_32.a，由于代码打算是用python来写，所以制作了一个动态库libfetion.so\n具体步骤如下：\n#解压静态库为*.o\nar -x libfetion_32.a\n#重新封装为so\ng++ -shared -Wall -fPIC -lcurl -pthread *.o -o libfetion.so\n#查看so[可选步骤]\nnm libfetion.so\n发送天气预报的python代码如下，libfetion.so中具体的方法请查看trunk/qt4_src/libfetion/include/libfetion/libfetion.h\n1 #!/usr/bin/env python\n2 #coding=utf-8\n3 #only can run at linux\n4 import os\n5 import ctypes\n6 import urllib2\n7\n8 #城市代码列表\n9 city_codes = {\n10 \u0026lsquo;合肥\u0026rsquo; : \u0026lsquo;CHXX0448\u0026rsquo;,","title":"用飞信(libfetion)定时发天气预报"},{"content":"最近为学校搭建了一个校园电子地图，做得不怎么好，只能凑合着用了。\n快回家了，做点记录，欢迎拍砖指正。\n新生应该可以在录取通知书里看到这个地图的链接了^_^\n刚开始做这个地图的时候，打算使用geoserver等专业的东西，时间太紧了，对GIS一点了解都没有，所以就放弃了。看到了6D校园和e都市的实现方案，找了一找，发现了mapeasy这个js库，于是把6D校园的地图图片下载下来了，自己试着用mapeasy实现了一下，效果很好，但是总感觉不对劲，像6D校园和e都市都是使用html map area来标注地图信息的，这样就与真正的地理信息系统差远了，不好快速的添加信息或者做路径搜索了，于是又不是很满意。在使用geoserver的时候，注意到它显示地图信息使用了openlayers，于是简单看了一下openlayers的例子，感觉很强大，也比较符合我的想法。\nopenlayers可以直接使用图片做地图的来源，然后直接编辑地图，添加地图的地理信息，包括点，线，多边形区域，这样就很容易做路径搜索，也很快速容易添加地图的地理信息了。\n看到openlayers可以使用tilecache做地图的图片来源，我就搭建了tilecache服务器，这个tilecache是用python写的，很容易配置。我仔细看了一下tilecache的配置，发现这个完全可以直接使用图片就可以，只是把切好的图片按照titlecache的目录结构放置好，就行了。于是简单的修改了一下openlayers的tilecache接口，就基本实现了电子地图。地图原图是一个面积很大图片，我用PIL库，使用python写了一个简单的切图程序，把地图按照横纵坐标切成了256X256大小的图片，并且按照自己定制tilecache的目录结构存好图片，最后把这些图片放在apache http服务器下，使用openlayers直接读取即可。\n最后实现的功能有：地图浏览，地图缩略展示，地面建筑标记，地面建筑提示，路线标记，快速定位，后台添加信息等(路径搜索暂未实现)\n其中后台可以直接画出建筑的多边形、路线、点标记，然后添加信息\n后台管理页面如下：\n","permalink":"https://lewang.dev/posts/2009-07-10-e-campus/","summary":"最近为学校搭建了一个校园电子地图，做得不怎么好，只能凑合着用了。\n快回家了，做点记录，欢迎拍砖指正。\n新生应该可以在录取通知书里看到这个地图的链接了^_^\n刚开始做这个地图的时候，打算使用geoserver等专业的东西，时间太紧了，对GIS一点了解都没有，所以就放弃了。看到了6D校园和e都市的实现方案，找了一找，发现了mapeasy这个js库，于是把6D校园的地图图片下载下来了，自己试着用mapeasy实现了一下，效果很好，但是总感觉不对劲，像6D校园和e都市都是使用html map area来标注地图信息的，这样就与真正的地理信息系统差远了，不好快速的添加信息或者做路径搜索了，于是又不是很满意。在使用geoserver的时候，注意到它显示地图信息使用了openlayers，于是简单看了一下openlayers的例子，感觉很强大，也比较符合我的想法。\nopenlayers可以直接使用图片做地图的来源，然后直接编辑地图，添加地图的地理信息，包括点，线，多边形区域，这样就很容易做路径搜索，也很快速容易添加地图的地理信息了。\n看到openlayers可以使用tilecache做地图的图片来源，我就搭建了tilecache服务器，这个tilecache是用python写的，很容易配置。我仔细看了一下tilecache的配置，发现这个完全可以直接使用图片就可以，只是把切好的图片按照titlecache的目录结构放置好，就行了。于是简单的修改了一下openlayers的tilecache接口，就基本实现了电子地图。地图原图是一个面积很大图片，我用PIL库，使用python写了一个简单的切图程序，把地图按照横纵坐标切成了256X256大小的图片，并且按照自己定制tilecache的目录结构存好图片，最后把这些图片放在apache http服务器下，使用openlayers直接读取即可。\n最后实现的功能有：地图浏览，地图缩略展示，地面建筑标记，地面建筑提示，路线标记，快速定位，后台添加信息等(路径搜索暂未实现)\n其中后台可以直接画出建筑的多边形、路线、点标记，然后添加信息\n后台管理页面如下：","title":"使用Openlayers搭一个E校园(e-campus),类似e都市，6d校园"},{"content":"初学jquery，觉得很好玩。欢迎拍砖指正\n我实现的效果截图\nmsn的效果截图\n全部是js实现的，效果还不错，可以IE7，ff3，Chrome上正常运行,这里是demo[查看]\n其中使用了jquery1.3.2[下载]，jquery.cycle.all.js[下载] 所有文件在这里[下载]\n1 body {\n2 font-family: Arial, Helvetica, sans-serif;\n3 font-size: 12px;\n4 font-style: normal;\n5 line-height: normal;\n6 font-weight: normal;\n7 }\n8 .clear{\n9 clear:both;\n10 height:0px;\n11 }\n12 .hot {\n13 background-color: #F7F7F7;\n14 height: 227px;\n15 width: 302px;\n16 border: 1px solid #DEDEDE;\n17 padding-right:5px;\n18 }\n19\n20 .hot_pic {\n21 height: 190px;\n22 width: 250px;\n23 padding: 0;\n24 margin: 0;\n25 float:left;\n26 clear:left;\n27 }\n28\n29 .hot_pic img {\n30 background-color: #eee;\n31 width: 240px;\n32 height: 180px;\n33 top: 0;\n34 left: 0 ;\n35 padding: 5px;\n36 }\n37 .hot_thumb{\n38 float:right;\n39 clear:right;\n40 height:190px;\n41 width:52px;\n42 }\n43 .hot_thumb a { width: 52px; height:48px; display: block;padding- top:0px;margin-top:0px;}\n44 .hot_thumb a.activeSlide {\n45 background-image: url(activeSlide.png);\n46 }\n47 .hot_thumb a:focus { outline: none; }\n48 .hot_thumb img { border: none; margin-top:4px;margin-right:4px;margin- bottom:4px;margin-left:8px;}\n49\n50 .hot_title p{margin:0 0;line-height:18px;padding-left:5px;}\n51\n1 1 \u003c!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\"\u003e\n2 3 4 5 图片切换\n6 7 8 9 35 36\n37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52\n","permalink":"https://lewang.dev/posts/2009-07-09-jquery-msn/","summary":"初学jquery，觉得很好玩。欢迎拍砖指正\n我实现的效果截图\nmsn的效果截图\n全部是js实现的，效果还不错，可以IE7，ff3，Chrome上正常运行,这里是demo[查看]\n其中使用了jquery1.3.2[下载]，jquery.cycle.all.js[下载] 所有文件在这里[下载]\n1 body {\n2 font-family: Arial, Helvetica, sans-serif;\n3 font-size: 12px;\n4 font-style: normal;\n5 line-height: normal;\n6 font-weight: normal;\n7 }\n8 .clear{\n9 clear:both;\n10 height:0px;\n11 }\n12 .hot {\n13 background-color: #F7F7F7;\n14 height: 227px;\n15 width: 302px;\n16 border: 1px solid #DEDEDE;\n17 padding-right:5px;\n18 }\n19\n20 .hot_pic {\n21 height: 190px;\n22 width: 250px;\n23 padding: 0;","title":"使用jquery+jQuery Cycle Plugin实现MSN首页图片滑动切换效果"},{"content":"刚刚登录appengine的时候才发现开始支持java了。\n上午的时候还没有。\n对java的比较熟悉，但是还是喜欢比较简洁的python\nhttp://code.google.com/intl/zh-CN/appengine/docs/java/overview.html\n\u0026lt;http://googleappengine.blogspot.com/2009/04/seriously-this-time-new-language- on-app.html\u0026gt;\n这是注册试用后google appengine给发的邮件:\nHello,\nThanks for signing up to try Google App Engine for Java! Your account has now\nbeen activated, so you can begin building applications with our new Java\nruntime.\nPlease note that this is still an early look at our support for the Java\nlanguage and you may encounter issues during development. We look forward to\nhearing your feedback! To learn more, please visit:\nhttp://code.google.com/appengine/docs/java/overview.html\nTo start creating applications with Google App Engine, simply follow this link\n(you may need to sign in with your burgundy.loh@gmail.com Google Account) and\nverify your account with a mobile phone number if you have not already done so:\nhttp://appengine.google.com/\nIf you are having trouble verifying your account via SMS please fill\nout the following form and your account will be reviewed and enabled\nas soon as possible:\nhttp://appengine.google.com/waitlist/sms_issues\nThanks!\nThe Google App Engine Team\nJava is a registered trademark of Sun Microsystems, Inc.\n","permalink":"https://lewang.dev/posts/2009-04-08-appenginejavaoverview/","summary":"刚刚登录appengine的时候才发现开始支持java了。\n上午的时候还没有。\n对java的比较熟悉，但是还是喜欢比较简洁的python\nhttp://code.google.com/intl/zh-CN/appengine/docs/java/overview.html\n\u0026lt;http://googleappengine.blogspot.com/2009/04/seriously-this-time-new-language- on-app.html\u0026gt;\n这是注册试用后google appengine给发的邮件:\nHello,\nThanks for signing up to try Google App Engine for Java! Your account has now\nbeen activated, so you can begin building applications with our new Java\nruntime.\nPlease note that this is still an early look at our support for the Java\nlanguage and you may encounter issues during development. We look forward to\nhearing your feedback! To learn more, please visit:","title":"appengine支持Java了"},{"content":"以教程中的一段小诗开始：\nProgramming is fun\nWhen the work is done\nif you wanna make your work also fun:\nuse Python!\n1 #!/usr/bin/env python\n2 #coding=utf-8\n3 poem = \u0026lsquo;\u0026rsquo;\u0026rsquo;\\\n4 Programming is fun\n5 When the work is done\n6 if you wanna make your work also fun:\n7 use Python!\n8 \u0026rsquo;\u0026rsquo;\u0026rsquo;\n9\n10 f = file(\u0026lsquo;c:\\\\poem.txt\u0026rsquo;,\u0026lsquo;w\u0026rsquo;)\n11 f.write(poem)\n12 f.close()\n哇，下面这段真是解释性的脚本语言的魅力啊，动态生成语句，然后直接解释执行，太灵活了\n1 #!/usr/bin/env python\n2 #coding=utf-8\n3 stm = \u0026lsquo;a = 10\u0026rsquo;\n4 exec(stm)\n5 print a\n直接输出了a的值10\n还有，更厉害的是可以给对象动态的添加属性，不知道这么理解对不对\n1 #!/usr/bin/env python\n2 #coding=utf-8\n3 class Person:\n4 def init(self, tel, mobile, email, address, elseInfo):\n5 self.tel = tel\n6 self.mobile = mobile\n7 self.email = email\n8 self.address = address\n9\n10 for key, value in elseInfo.items():\n11 stm = \u0026ldquo;self.%s = \u0026quot;%s\u0026quot;\u0026rdquo; % (key, value)\n12 exec(stm)\n13\n14 loh = Person(\u0026lsquo;2***3\u0026rsquo;,\u0026lsquo;1368533\u0026rsquo;,\u0026rsquo;elgnaw(at)tju.edu.cn\u0026rsquo;,\u0026lsquo;Tianjin University\u0026rsquo;,email2 = \u0026lsquo;burgundy.loh(at)gmail.com\u0026rsquo;,mobile2 = \u0026lsquo;136***211\u0026rsquo;)\n15\n16 print loh.email\n17 print loh.email2\n18 print dir(loh)\n19\n输出为\nelgnaw(at)tju.edu.cn\nburgundy.loh(at)gmail.com\n[\u0026rsquo;doc\u0026rsquo;, \u0026lsquo;init\u0026rsquo;, \u0026lsquo;module\u0026rsquo;, \u0026lsquo;address\u0026rsquo;, \u0026rsquo;email\u0026rsquo;, \u0026rsquo;email2\u0026rsquo;, \u0026lsquo;mobile\u0026rsquo;, \u0026lsquo;mobile2\u0026rsquo;, \u0026rsquo;tel\u0026rsquo;]\n这里是python的教程：简明 Python 教程\n也开始你的python之路吧\n","permalink":"https://lewang.dev/posts/2009-03-25-python-start/","summary":"以教程中的一段小诗开始：\nProgramming is fun\nWhen the work is done\nif you wanna make your work also fun:\nuse Python!\n1 #!/usr/bin/env python\n2 #coding=utf-8\n3 poem = \u0026lsquo;\u0026rsquo;\u0026rsquo;\\\n4 Programming is fun\n5 When the work is done\n6 if you wanna make your work also fun:\n7 use Python!\n8 \u0026rsquo;\u0026rsquo;\u0026rsquo;\n9\n10 f = file(\u0026lsquo;c:\\\\poem.txt\u0026rsquo;,\u0026lsquo;w\u0026rsquo;)\n11 f.write(poem)\n12 f.close()\n哇，下面这段真是解释性的脚本语言的魅力啊，动态生成语句，然后直接解释执行，太灵活了\n1 #!/usr/bin/env python\n2 #coding=utf-8\n3 stm = \u0026lsquo;a = 10\u0026rsquo;","title":"初识python"},{"content":"经纬度数据收集自 维北有斗-天文爱好者的乐园\n编写这篇博文有几个目的：\n首先认识了两个单词\nlatitude [lat·i·tude || \u0026rsquo;lætɪtuːd /-tjuːd] n. 纬度; 回旋余地, 自由; 纬度地区\nlongitude [lon·gi·tude || \u0026rsquo;lɑndʒɪtuːd /\u0026rsquo;lɒndʒɪtjuːd] n. 经度; 经线\n1.学习dom4j的简单用法\n2.做一个方便使用的全国城市经纬度数据库\n3.利用google天气api做一个全面的天气预报服务\n4.学习用python编写webservice\n内容：\n1.学习dom4j的简单用法\n收集了的经纬度都是按照城市分开的单独的htm文件，我已经处理过为符合html4.0标准的html文件了，即所有的标识都是成对的，这里可以下载这些文件(点击下载经纬度html包)\n(1)如何使用dom4j读入xml文档\n使用dom4j读入xml文档代码片段\n1 File file = new File(\u0026ldquo;G:\\\\workspace\\\\test.xml\u0026rdquo;);\n2 SAXReader reader = new SAXReader();\n3 //根据实际情况设置编码\n4 reader.setEncoding(\u0026ldquo;GBK\u0026rdquo;);\n5 Document doc = reader.read(files);\n6\n7 //获得xml文档的根节点\n8 Element root = doc.getRootElement();\n(2)如何遍历xml文档的所有结点\n这样就读入了这个xml文档，并获得了这个xml文档对象的根结点，由于xml文档是一个树形结构，这样就很容易遍历它所有的结点了。\n这里根据自己的需要，测试使用了两种遍历方法，一种是根据树的结构编写递归程序，一种就是直接使用循环，还有都dom4j自带的visitor模式访问也很好用，这里有篇参考文档，可以看看(查看)\n使用递归遍历\npublic void visitAll(Element root, Element child){ // 枚举所有子节点 for ( Iterator i = root.elementIterator(); i.hasNext(); ) { Element element = (Element) i.next(); System.out.println(stringTrim(element.getText())); if(this.stringTrim(element.getTextTrim()).equals(\u0026quot;\u0026quot;)){ return; } else { Element tmp = child.addElement(\u0026quot;city\u0026quot;); visitAll(element, tmp); } } } 这里提供的xml文档层数为2，直接循环即可\n直接循环遍历\npublic void visitAll(Element root, Element child){ // 枚举所有子节点 for ( Iterator i = root.elementIterator(); i.hasNext(); ) { Element element = (Element) i.next(); Iterator j = element.elementIterator(); Element tmpElement = (Element)j.next(); if(!(stringTrim(tmpElement.getTextTrim()).equals(\u0026quot;城市名\u0026quot;))) { Element tmp = child.addElement(\u0026quot;city\u0026quot;); tmp.addAttribute(\u0026quot;name\u0026quot;, stringTrim(tmpElement.getTextTrim()).trim()); tmpElement = (Element)j.next(); tmp.addAttribute(\u0026quot;longitude\u0026quot;, stringTrim(tmpElement.getTextTrim()).trim()); tmpElement = (Element)j.next(); tmp.addAttribute(\u0026quot;latitude\u0026quot;, stringTrim(tmpElement.getTextTrim()).trim()); } } } 关于visitor模式的使用，请参看上面给出的参考文档\n(3)创建新的xml文档\n创建xml文档\npublic void createXMLFile(String filename) throws Exception{ //使用辅助方法创建文档对象 Document res = DocumentHelper.createDocument(); //添加文档跟结点country Element rootElement = res.addElement(\u0026quot;country\u0026quot;); //给根节点添加属性name，值为\u0026quot;中国\u0026quot; rootElement.addAttribute(\u0026quot;name\u0026quot;, \u0026quot;中国\u0026quot;); //打开目录 File file = new File(\u0026quot;G:\\\\workspace\\\\jwd\\\\jwd\u0026quot;); //打开目录下所有的xml文档 File[] files = file.listFiles(); //依次操作每个xml文档 for(int i = 0; i \u0026lt; files.length; i ++){ //获取文件名，去掉文件扩展名 String[] name = files[i].getName().split(\u0026quot;\\\\.\u0026quot;); //在根结点下添加子节点province Element childElement = rootElement.addElement(\u0026quot;province\u0026quot;); //添加属性name，值为文件名 childElement.addAttribute(\u0026quot;name\u0026quot;, name[0].trim()); //创建reader SAXReader reader = new SAXReader(); //根据实际情况设置编码 reader.setEncoding(\u0026quot;GBK\u0026quot;); Document doc = reader.read(files[i]); //获得xml文档的根节点 Element root = doc.getRootElement(); //遍历所有的结点 this.visitAll(root, childElement); } //格式化即将输出的xml，让它看起来整齐点 OutputFormat format = OutputFormat.createPrettyPrint(); /*将document中的内容写入文件中 */ XMLWriter writer = new XMLWriter(new FileWriter(new File(filename)),format); writer.write(res); writer.close(); } 整个合并所有html页为一个xml的代码如下\n完整的代码\nimport java.io.File; import java.io.FileWriter; import java.util.Iterator; import org.dom4j.Document; import org.dom4j.DocumentHelper; import org.dom4j.Element; import org.dom4j.io.OutputFormat; import org.dom4j.io.SAXReader; import org.dom4j.io.XMLWriter; /** * @author canvas * */ public class GIS { public void createXMLFile(String filename) throws Exception{ //使用辅助方法创建文档对象 Document res = DocumentHelper.createDocument(); //添加文档跟结点country Element rootElement = res.addElement(\u0026quot;country\u0026quot;); //给根节点添加属性name，值为\u0026quot;中国\u0026quot; rootElement.addAttribute(\u0026quot;name\u0026quot;, \u0026quot;中国\u0026quot;); //打开目录 File file = new File(\u0026quot;G:\\\\workspace\\\\jwd\\\\jwd\u0026quot;); //打开目录下所有的xml文档 File[] files = file.listFiles(); //依次操作每个xml文档 for(int i = 0; i \u0026lt; files.length; i ++){ //获取文件名，去掉文件扩展名 String[] name = files[i].getName().split(\u0026quot;\\\\.\u0026quot;); //在根结点下添加子节点province Element childElement = rootElement.addElement(\u0026quot;province\u0026quot;); //添加属性name，值为文件名 childElement.addAttribute(\u0026quot;name\u0026quot;, name[0].trim()); //创建reader SAXReader reader = new SAXReader(); //根据实际情况设置编码 reader.setEncoding(\u0026quot;GBK\u0026quot;); Document doc = reader.read(files[i]); //获得xml文档的根节点 Element root = doc.getRootElement(); //遍历所有的结点 this.visitAll(root, childElement); } //格式化即将输出的xml，让它看起来整齐点 OutputFormat format = OutputFormat.createPrettyPrint(); /*将document中的内容写入文件中 */ XMLWriter writer = new XMLWriter(new FileWriter(new File(filename)),format); writer.write(res); writer.close(); } public void visitAll(Element root, Element child){ // 枚举所有子节点 for ( Iterator i = root.elementIterator(); i.hasNext(); ) { Element element = (Element) i.next(); //System.out.println(stringTrim(element.getText())); /*if(this.stringTrim(element.getTextTrim()).equals(\u0026quot;\u0026quot;)){ return; } else { Element tmp = child.addElement(\u0026quot;city\u0026quot;); visitAll(element, tmp); }*/ Iterator j = element.elementIterator(); Element tmpElement = (Element)j.next(); if(!(stringTrim(tmpElement.getTextTrim()).equals(\u0026quot;城市名\u0026quot;))) { Element tmp = child.addElement(\u0026quot;city\u0026quot;); tmp.addAttribute(\u0026quot;name\u0026quot;, stringTrim(tmpElement.getTextTrim()).trim()); tmpElement = (Element)j.next(); tmp.addAttribute(\u0026quot;longitude\u0026quot;, stringTrim(tmpElement.getTextTrim()).trim()); tmpElement = (Element)j.next(); tmp.addAttribute(\u0026quot;latitude\u0026quot;, stringTrim(tmpElement.getTextTrim()).trim()); } } } public String stringTrim(String str){ //去掉空格和全角空格 String[] tmp = str.split(\u0026quot;\\\\s| \u0026quot;); StringBuffer sb = new StringBuffer(); for(int i = 0; i \u0026lt; tmp.length; i ++){ sb.append(tmp[i].trim()); } return sb.toString(); } /** * @param args */ public static void main(String[] args) { try { new GIS().createXMLFile(\u0026quot;d:\\\\GI-China.xml\u0026quot;); } catch (Exception e) { e.printStackTrace(); } } } 2.做一个方便使用的全国城市经纬度数据库\n经过上面的操作，一个完整的全国城市经纬度数据库就做好了，只不过它是xml格式的，看起来还不错，[下载这个数据库](/Files/canvas/GI- China.zip \u0026ldquo;下载这个数据库\u0026rdquo;)\n生成的xml展示\n\u0026lt;?xml version=\u0026quot;1.0\u0026quot; encoding=\u0026quot;UTF-8\u0026quot;?\u0026gt; \u0026lt;country name=\u0026quot;中国\u0026quot;\u0026gt; \u0026lt;province name=\u0026quot;上海\u0026quot;\u0026gt; \u0026lt;city name=\u0026quot;上海\u0026quot; longitude=\u0026quot;121.48\u0026quot; latitude=\u0026quot;31.22\u0026quot;/\u0026gt; \u0026lt;city name=\u0026quot;嘉定\u0026quot; longitude=\u0026quot;121.24\u0026quot; latitude=\u0026quot;31.4\u0026quot;/\u0026gt; \u0026lt;city name=\u0026quot;宝山\u0026quot; longitude=\u0026quot;121.48\u0026quot; latitude=\u0026quot;31.41\u0026quot;/\u0026gt; \u0026lt;city name=\u0026quot;川沙\u0026quot; longitude=\u0026quot;121.7\u0026quot; latitude=\u0026quot;31.19\u0026quot;/\u0026gt; \u0026lt;city name=\u0026quot;南汇\u0026quot; longitude=\u0026quot;121.76\u0026quot; latitude=\u0026quot;31.05\u0026quot;/\u0026gt; \u0026lt;city name=\u0026quot;奉贤\u0026quot; longitude=\u0026quot;121.46\u0026quot; latitude=\u0026quot;30.92\u0026quot;/\u0026gt; \u0026lt;city name=\u0026quot;松江\u0026quot; longitude=\u0026quot;121.24\u0026quot; latitude=\u0026quot;31\u0026quot;/\u0026gt; \u0026lt;city name=\u0026quot;金山\u0026quot; longitude=\u0026quot;121.16\u0026quot; latitude=\u0026quot;30.89\u0026quot;/\u0026gt; \u0026lt;city name=\u0026quot;青浦\u0026quot; longitude=\u0026quot;121.1\u0026quot; latitude=\u0026quot;31.15\u0026quot;/\u0026gt; \u0026lt;city name=\u0026quot;崇明\u0026quot; longitude=\u0026quot;121.4\u0026quot; latitude=\u0026quot;31.73\u0026quot;/\u0026gt; \u0026lt;/province\u0026gt; \u0026lt;province name=\u0026quot;云南\u0026quot;\u0026gt; \u0026lt;city name=\u0026quot;昆明\u0026quot; longitude=\u0026quot;102.73\u0026quot; latitude=\u0026quot;25.04\u0026quot;/\u0026gt; \u0026lt;city name=\u0026quot;富民\u0026quot; longitude=\u0026quot;102.48\u0026quot; latitude=\u0026quot;25.21\u0026quot;/\u0026gt; \u0026lt;city name=\u0026quot;晋宁\u0026quot; longitude=\u0026quot;102.58\u0026quot; latitude=\u0026quot;24.68\u0026quot;/\u0026gt; \u0026lt;city name=\u0026quot;呈贡\u0026quot; longitude=\u0026quot;102.79\u0026quot; latitude=\u0026quot;24.9\u0026quot;/\u0026gt; \u0026lt;city name=\u0026quot;安宁\u0026quot; longitude=\u0026quot;102.44\u0026quot; latitude=\u0026quot;24.95\u0026quot;/\u0026gt; \u0026lt;city name=\u0026quot;昭通\u0026quot; longitude=\u0026quot;103.7\u0026quot; latitude=\u0026quot;29.32\u0026quot;/\u0026gt; \u0026lt;city name=\u0026quot;永善\u0026quot; longitude=\u0026quot;103.63\u0026quot; latitude=\u0026quot;28.22\u0026quot;/\u0026gt; \u0026lt;city name=\u0026quot;大关\u0026quot; longitude=\u0026quot;103.91\u0026quot; latitude=\u0026quot;27.74\u0026quot;/\u0026gt; \u0026lt;city name=\u0026quot;彝良\u0026quot; longitude=\u0026quot;104.06\u0026quot; latitude=\u0026quot;27.61\u0026quot;/\u0026gt; \u0026lt;city name=\u0026quot;鲁甸\u0026quot; longitude=\u0026quot;103.54\u0026quot; latitude=\u0026quot;27.21\u0026quot;/\u0026gt; \u0026lt;city name=\u0026quot;绥江\u0026quot; longitude=\u0026quot;103.97\u0026quot; latitude=\u0026quot;28.58\u0026quot;/\u0026gt; 3.利用google天气api做一个全面的天气预报服务\ngoogle提供的天气api可以有两种方式访问，\n一种是\n[ http://www.google.com/ig/api?hl=zh- cn\u0026amp;weather=,,,30670000,104019996](http://www.google.com/ig/api?hl=zh- cn\u0026amp;weather=,,,30670000,104019996)\n30670000为纬度，104019996为经度，这个是把上面的xml中的经纬度乘以了10^6(10的六次方)，这个方法有个好处，就是可以根据经纬度获得天气预报，自然预报地区也可变得精确些\n另一种是\nhttp://www.google.com/ig/api?weather=Beijing\n这个链接比较简单，但是支持的城市比较少，所以还是使用第一种方式比较好，于是才整理好了所有城市的经纬度\n访问上面的链接，google返回的xml内容看起来是这样的\ngoogle返回的xml\n1 \u003c?xml version=\"1.0\" encoding=\"UTF-8\"?\u003e\n2 \u0026lt;xml_api_reply version=\u0026ldquo;1\u0026rdquo;\u0026gt;\u0026lt;forecast_information\u0026gt;\u0026lt;postal_code data=\u0026quot;\u0026quot;/\u0026gt;\u0026lt;latitude_e6 data=\u0026ldquo;30670000\u0026rdquo;/\u0026gt;\u0026lt;longitude_e6 data=\u0026ldquo;104019996\u0026rdquo;/\u0026gt;\u0026lt;forecast_date data=\u0026ldquo;2009-03-05\u0026rdquo;/\u0026gt;\u0026lt;current_date_time data=\u0026ldquo;2009-03-05 19:00:00 +0000\u0026rdquo;/\u0026gt;\u0026lt;unit_system data=\u0026ldquo;SI\u0026rdquo;/\u0026gt;\u0026lt;/forecast_information\u0026gt;\u0026lt;current_conditions\u0026gt;\u0026lt;temp_f data=\u0026ldquo;61\u0026rdquo;/\u0026gt;\u0026lt;temp_c data=\u0026ldquo;16\u0026rdquo;/\u0026gt;\u0026lt;wind_condition data=\u0026ldquo;风向： 北、风速：6 (公里/小时）\u0026rdquo;/\u0026gt;\u0026lt;/current_conditions\u0026gt;\u0026lt;forecast_conditions\u0026gt;\u0026lt;day_of_week data=\u0026ldquo;周四\u0026rdquo;/\u0026gt;\u0026lt;/forecast_conditions\u0026gt;\u0026lt;forecast_conditions\u0026gt;\u0026lt;day_of_week data=\u0026ldquo;周五\u0026rdquo;/\u0026gt;\u0026lt;/forecast_conditions\u0026gt;\u0026lt;forecast_conditions\u0026gt;\u0026lt;day_of_week data=\u0026ldquo;周六\u0026rdquo;/\u0026gt;\u0026lt;/forecast_conditions\u0026gt;\u0026lt;forecast_conditions\u0026gt;\u0026lt;day_of_week data=\u0026ldquo;周日\u0026rdquo;/\u0026gt;\u0026lt;/forecast_conditions\u0026gt;\u0026lt;/xml_api_reply\u0026gt;\n对于返回的xml中的，可以这样构造链接获取google提供的图片\n取其中的cn_cloudy.gif，然后放在http://www.google.cn/ig/images/weather/之后，生成的链接是\nhttp://www.google.cn/ig/images/weather/cn_cloudy.png\n这样这个天气api就基本构思好了\n4.学习用python编写webservice(待续……)\n将这个服务编写为一个webservice，决定使用python去写，所以要学习一下python了\n","permalink":"https://lewang.dev/posts/2009-03-12-gs-china/","summary":"经纬度数据收集自 维北有斗-天文爱好者的乐园\n编写这篇博文有几个目的：\n首先认识了两个单词\nlatitude [lat·i·tude || \u0026rsquo;lætɪtuːd /-tjuːd] n. 纬度; 回旋余地, 自由; 纬度地区\nlongitude [lon·gi·tude || \u0026rsquo;lɑndʒɪtuːd /\u0026rsquo;lɒndʒɪtjuːd] n. 经度; 经线\n1.学习dom4j的简单用法\n2.做一个方便使用的全国城市经纬度数据库\n3.利用google天气api做一个全面的天气预报服务\n4.学习用python编写webservice\n内容：\n1.学习dom4j的简单用法\n收集了的经纬度都是按照城市分开的单独的htm文件，我已经处理过为符合html4.0标准的html文件了，即所有的标识都是成对的，这里可以下载这些文件(点击下载经纬度html包)\n(1)如何使用dom4j读入xml文档\n使用dom4j读入xml文档代码片段\n1 File file = new File(\u0026ldquo;G:\\\\workspace\\\\test.xml\u0026rdquo;);\n2 SAXReader reader = new SAXReader();\n3 //根据实际情况设置编码\n4 reader.setEncoding(\u0026ldquo;GBK\u0026rdquo;);\n5 Document doc = reader.read(files);\n6\n7 //获得xml文档的根节点\n8 Element root = doc.getRootElement();\n(2)如何遍历xml文档的所有结点\n这样就读入了这个xml文档，并获得了这个xml文档对象的根结点，由于xml文档是一个树形结构，这样就很容易遍历它所有的结点了。\n这里根据自己的需要，测试使用了两种遍历方法，一种是根据树的结构编写递归程序，一种就是直接使用循环，还有都dom4j自带的visitor模式访问也很好用，这里有篇参考文档，可以看看(查看)\n使用递归遍历\npublic void visitAll(Element root, Element child){ // 枚举所有子节点 for ( Iterator i = root.","title":"中国各城市的经纬度数据(精确到县级城市)"},{"content":"办公室通过路由器上网，所以不能直接远程桌面登录到自己计算机上，办公室的人都想在宿舍登录到自己的计算机上工作学习，所以可以简单的将路由器的3389端口(windows远程桌面默认使用3389端口)映射到自己的IP上，同时修改其他人计算机的远程桌面端口，然后再相应的将路由器端口映射到自己的IP上即可。\n修改远程桌面端口需要修改注册表中的两个值：\n[HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\Terminal Server\\WinStations\\RDP-Tcp]\n\u0026ldquo;PortNumber\u0026rdquo;=dword:00000d3d\n[HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\Terminal Server\\Wds\\rdpwd\\Tds\\tcp]\n\u0026ldquo;PortNumber\u0026rdquo;=dword:00000d3d\n修改两个值，十进制是0-65535，双字十六进制是0x00000000-0x0000FFFF，推荐修改为1024以上。\n如何使用windows api编写一个小程序来修改端口呢？接下来就详细的讲讲这个程序应该如何实现，另外在讨论一下如何使用windows api编写简单的windows程序。\n下载开源的windows api集成开发环境Code::Blocks (访问Code::Blocks主页)\n建议下载集成mingw的CodeBlocks，这样就不用单独下载安装mingw了，如果不知道什么是mingw，赶紧google一下看看吧。配置好开发环境后，就可以开始下一步啦。\n启动Code::Blocks，然后新建一个Win32 GUI Project，这样IDE会自动构建一个对话框，而且程序的基本结构也就构造好了。程序的入口就是WinMain函数，然后就可以在这个基础之上编写我们的程序了。\n先确定这个程序有几个窗口，然后修改resource.rc文件，资料都可以去MSDN查找\nresource.rc\n1 #include \u0026ldquo;resource.h\u0026rdquo;\n2 3 DLG_MAIN DIALOGEX 6,6,269,131\n4 STYLE 0x90CC0800\n5 CAPTION \u0026ldquo;远程桌面连接端口修改器\u0026rdquo;\n6 FONT 8,\u0026ldquo;MS Sans Serif\u0026rdquo;,0,0\n7 BEGIN\n8 CONTROL \u0026ldquo;\u0026quot;,IDC_EDT,\u0026ldquo;Edit\u0026rdquo;,0x50010000,72,51,170,13,0x00000200\n9 CONTROL \u0026ldquo;十进制(\u0026amp;D)\u0026quot;,IDC_RBN_D,\u0026ldquo;Button\u0026rdquo;,0x50010009,20,81,54,9\n10 CONTROL \u0026ldquo;十六进制(\u0026amp;H)\u0026quot;,IDC_RBN_H,\u0026ldquo;Button\u0026rdquo;,0x50010009,90,81,60,9\n11 CONTROL \u0026ldquo;二进制(\u0026amp;B)\u0026quot;,IDC_RBN_B,\u0026ldquo;Button\u0026rdquo;,0x50010009,164,81,54,9\n12 CONTROL \u0026ldquo;修改(\u0026amp;E)\u0026quot;,IDC_BTN_E,\u0026ldquo;Button\u0026rdquo;,0x50010000,14,107,54,13\n13 CONTROL \u0026ldquo;还原(\u0026amp;B)\u0026quot;,IDC_BTN_B,\u0026ldquo;Button\u0026rdquo;,0x50010000,70,107,54,13\n14 CONTROL \u0026ldquo;关于(\u0026amp;A)\u0026quot;,IDC_BTN_A,\u0026ldquo;Button\u0026rdquo;,0x50010000,126,107,54,13\n15 CONTROL \u0026ldquo;退出(\u0026amp;Q)\u0026quot;,IDC_BTN_Q,\u0026ldquo;Button\u0026rdquo;,0x50010000,198,107,54,13\n16 CONTROL \u0026ldquo;远程桌面端口\u0026rdquo;,IDC_STC,\u0026ldquo;Static\u0026rdquo;,0x50000000,16,53,52,9\n17 CONTROL \u0026ldquo;基数\u0026rdquo;,IDC_GRP,\u0026ldquo;Button\u0026rdquo;,0x50000007,14,70,228,24\n18 CONTROL \u0026ldquo;#1013\u0026rdquo;,IDC_IMG,\u0026ldquo;Static\u0026rdquo;,0x5000020E,0,0,271,44\n19 END\n20 ICON_LOH ICON DISCARDABLE \u0026ldquo;rd.ico\u0026rdquo;\n21 BMP_HEAD BITMAP DISCARDABLE \u0026ldquo;head.bmp\u0026rdquo;\n22 23 IDD_DLG_ABOUT DIALOGEX 55,25,151,55\n24 CAPTION \u0026ldquo;关于\u0026rdquo;\n25 FONT 8,\u0026ldquo;MS Sans Serif\u0026rdquo;,0,0\n26 STYLE 0x90CC0000\n27 BEGIN\n28 CONTROL \u0026ldquo;#1012\u0026rdquo;,IDC_IMG_ABOUT,\u0026ldquo;Static\u0026rdquo;,0x50000203,14,11,20,19\n29 CONTROL \u0026ldquo;远程桌面链接端口修改器\\nloh.wong@qq.com\u0026rdquo;,IDC_STC_ABOUT,\u0026ldquo;Static\u0026rdquo;,0x50000000,42,11,94,20\n30 CONTROL \u0026ldquo;确定\u0026rdquo;,IDC_BTN_ABOUT,\u0026ldquo;Button\u0026rdquo;,0x50010000,106,38,40,13\n31 END\n32 resource.rc文件很简单，包括两个对话框，同时对话框里包括了一些简单的控件。\n修改resource.h文件，在其中添加一些控件的预定义\nresource.h\n1 #include \u0026lt;windows.h\u0026gt;\n2 3 // ID of Main Dialog\n4 #define DLG_MAIN 1000\n5 6 // ID of Button Controls\n7 #define IDC_GRP 1003\n8 #define IDC_STC 1001\n9 #define IDC_EDT 1002\n10 #define IDC_RBN_H 1004\n11 #define IDC_RBN_D 1005\n12 #define IDC_BTN_E 1006\n13 #define IDC_BTN_B 1007\n14 #define IDC_BTN_Q 1008\n15 #define IDC_BTN_A 1009\n16 #define IDC_IMG 1010\n17 #define IDC_RBN_B 1011\n18 #define ICON_LOH 1012\n19 #define BMP_HEAD 1013\n20 21 //About Dialog\n22 #define IDD_DLG_ABOUT 1017\n23 #define IDC_STC_ABOUT 1014\n24 #define IDC_BTN_ABOUT 1015\n25 #define IDC_IMG_ABOUT 1016\n26 编写一些简单的工具函数，主要实现2进制、10进制和16进制之间的相互转换，和一些输入的校验。这个程序做的不是很完善，没有校验一些输入数据。\n这里是utils.h文件(H代表16进制，D代表10进制，B代表2进制)\nutils.h\n1 #ifndef UTILS_H_INCLUDED\n2 #define UTILS_H_INCLUDED\n3 4 #define H 16\n5 #define D 10\n6 #define B 2\n7 8 #define RD_FALSE 0\n9 #define RD_TRUE 1\n10 11 ///格式转换/\n12 char * strReverse(char *);\n13 char * DtoDstr(int);\n14 int CommontoD(char *, int);\n15 16 char * HtoD(char *);\n17 char * BtoD(char *);\n18 19 char * HtoB(char *);\n20 char * DtoB(char *);\n21 22 char * BtoH(char *);\n23 char * DtoH(char *);\n24 25 ///格式检查/\n26 int HFormatCheck(char *);\n27 int BFormatCheck(char *);\n28 int DFormatCheck(char *);\n29 30 #endif // UTILS_H_INCLUDED\n31 这里是utils.c文件\nutils.c\n1 #include \u0026lt;stdio.h\u0026gt;\n2 #include \u0026lt;stdlib.h\u0026gt;\n3 #include \u0026lt;string.h\u0026gt;\n4 #include \u0026lt;math.h\u0026gt;\n5 #include \u0026ldquo;utils.h\u0026rdquo;\n6 7 //字符串逆序\n8 char * strReverse(char str)\n9 {\n10 int i,j,len = strlen(str);\n11 char * res = (char)malloc(len\n1);\n12 memset(res, 0, len + 1);\n13 for(i = len - 1, j = 0; i \u0026gt;= 0; i --, j ++) {\n14 res[j] = str[i];\n15 }\n16 return res;\n17 }\n18 19 //2，16进制转10进制\n20 int CommontoD(char *pC, int factor)\n21 {\n22 pC = strReverse(pC);\n23 int d_res = 0;\n24 int d_num = 0;\n25 int i;\n26 for(i = strlen(pC) - 1; i \u0026gt;= 0; i --)\n27 {\n28 if(pC[i] \u0026gt;= \u0026lsquo;0\u0026rsquo; \u0026amp;\u0026amp; pC[i] \u0026lt;= \u0026lsquo;9\u0026rsquo;) {\n29 d_num = pC[i] - \u0026lsquo;0\u0026rsquo;;\n30 }\n31 if(pC[i] \u0026gt;= \u0026lsquo;a\u0026rsquo; \u0026amp;\u0026amp; pC[i] \u0026lt;= \u0026lsquo;f\u0026rsquo;) {\n32 d_num = pC[i] - 87;\n33 }\n34 if(pC[i] \u0026gt;= \u0026lsquo;A\u0026rsquo; \u0026amp;\u0026amp; pC[i] \u0026lt;= \u0026lsquo;F\u0026rsquo;) {\n35 d_num = pC[i] - 55;\n36 }\n37 d_res += d_num * pow(factor, i);\n38 }\n39 return d_res;\n40 }\n41 42 //将10进制数转为字符串\n43 char * DtoDstr(int d_num)\n44 {\n45 char d_str = (char)malloc(6);\n46 memset(d_str,0,6);\n47 int i = 0;\n48 while(d_num \u0026gt; 0)\n49 {\n50 d_str[i++] = (d_num % 10) + \u0026lsquo;0\u0026rsquo;;\n51 d_num /= 10;\n52 }\n53 return strReverse(d_str);\n54 }\n55 //二进制到十进制\n56 char * BtoD(char *pB)\n57 {\n58 return DtoDstr(CommontoD(pB, B));\n59 }\n60 61 //十六进制到十进制\n62 char * HtoD(char *pH)\n63 {\n64 return DtoDstr(CommontoD(pH, H));\n65 }\n66 67 68 //十进制到二进制\n69 char * DtoB(char *pD)\n70 {\n71 //65535用16个1表示\n72 //开17个就够了\n73 char b_str = (char)malloc(17);\n74 memset(b_str,0,17);\n75 int d_num = atoi(pD);\n76 int i = 0;\n77 while(d_num \u0026gt; 0) {\n78 b_str[i++] = (d_num % 2) + \u0026lsquo;0\u0026rsquo;;\n79 d_num /= 2;\n80 }\n81 return strReverse(b_str);\n82 }\n83 84 //十六进制转二进制\n85 char * HtoB(char *pH)\n86 {\n87 return DtoB(HtoD(pH));\n88 }\n89 90 //十进制到十六进制\n91 char * DtoH(char *pD)\n92 {\n93 //65535用4个f表示\n94 //开5个就够了\n95 char h_str = (char)malloc(5);\n96 memset(h_str,0,5);\n97 int d_num = atoi(pD);\n98 int bit;\n99 int i = 0;\n100 while(d_num \u0026gt; 0) {\n101 bit = d_num % 16;\n102 if(bit \u0026gt;= 0 \u0026amp;\u0026amp; bit \u0026lt;= 9) {\n103 h_str[i++] = bit + \u0026lsquo;0\u0026rsquo;;\n104 }\n105 else {\n106 h_str[i++] = bit + 87;\n107 }\n108 d_num /= 16;\n109 }\n110 return strReverse(h_str);\n111 }\n112 113 //二进制转十六进制\n114 char * BtoH(char *pB)\n115 {\n116 return DtoH(BtoD(pB));\n117 }\n118 119 //检查16进制数是否合法\n120 int HFormatCheck(char *pH)\n121 {\n122 if(pH == NULL) {\n123 return RD_FALSE;\n124 }\n125 126 int len = strlen(pH);\n127 int i;\n128 for(i = 0; i \u0026lt; len; i ++)\n129 {\n130 if(!((pH[i] \u0026gt;= \u0026lsquo;0\u0026rsquo; \u0026amp;\u0026amp; pH[i] \u0026lt;= \u0026lsquo;9\u0026rsquo;) || (pH[i] \u0026gt;= \u0026lsquo;a\u0026rsquo; \u0026amp;\u0026amp; pH[i] \u0026lt;= \u0026lsquo;f\u0026rsquo;) || (pH[i] \u0026gt;= \u0026lsquo;A\u0026rsquo; \u0026amp;\u0026amp; pH[i] \u0026lt;= \u0026lsquo;F\u0026rsquo;) ))\n131 {\n132 return RD_FALSE;\n133 }\n134 }\n135 return RD_TRUE;\n136 }\n137 //检查2进制数是否合法\n138 int BFormatCheck(char *pH)\n139 {\n140 if(pH == NULL) {\n141 return RD_FALSE;\n142 }\n143 144 int len = strlen(pH);\n145 int i;\n146 for(i = 0; i \u0026lt; len; i ++)\n147 {\n148 if(!(pH[i] \u0026gt;= \u0026lsquo;0\u0026rsquo; \u0026amp;\u0026amp; pH[i] \u0026lt;= \u0026lsquo;1\u0026rsquo;))\n149 {\n150 return RD_FALSE;\n151 }\n152 }\n153 return RD_TRUE;\n154 }\n155 156 //检查10进制数是否合法\n157 int DFormatCheck(char *pH)\n158 {\n159 if(pH == NULL) {\n160 return RD_FALSE;\n161 }\n162 163 int len = strlen(pH);\n164 int i;\n165 for(i = 0; i \u0026lt; len; i ++)\n166 {\n167 if(!(pH[i] \u0026gt;= \u0026lsquo;0\u0026rsquo; \u0026amp;\u0026amp; pH[i] \u0026lt;= \u0026lsquo;9\u0026rsquo;))\n168 {\n169 return RD_FALSE;\n170 }\n171 }\n172 return RD_TRUE;\n173 }\n174 175 176 最后是主程序main.c\nmain.c\n1 #include \u0026lt;windows.h\u0026gt;\n2 #include \u0026lt;stdio.h\u0026gt;\n3 #include \u0026lt;stdlib.h\u0026gt;\n4 5 #include \u0026ldquo;utils.h\u0026rdquo;\n6 #include \u0026ldquo;resource.h\u0026rdquo;\n7 8 HINSTANCE hInst;\n9 HICON hIcon;\n10 int id_pre_checked;\n11 int port;\n12 char *buf;\n13 14 //两个注册表\n15 HKEY winstations;\n16 HKEY wds;\n17 18 //获得十进制的数\n19 int GetPortNumber(char *port, int IDC)\n20 {\n21 //char * res;\n22 if(IDC == IDC_RBN_D)\n23 {\n24 return atoi(port);\n25 }\n26 if(IDC == IDC_RBN_H)\n27 {\n28 return atoi(HtoD(port));\n29 }\n30 if(IDC == IDC_RBN_B)\n31 {\n32 return atoi(BtoD(port));\n33 }\n34 return -1;\n35 }\n36 37 //关于窗口\n38 BOOL CALLBACK AboutDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)\n39 {\n40 switch(uMsg)\n41 {\n42 case WM_INITDIALOG:\n43 SendMessage(hwndDlg,WM_SETICON,ICON_BIG,(LPARAM)hIcon);\n44 //SendMessage(hwndDlg,WM_SETICON,ICON_SMALL,(LPARAM)hIcon);\n45 return TRUE;\n46 case WM_CLOSE:\n47 EndDialog(hwndDlg, 0);\n48 return TRUE;\n49 50 case WM_COMMAND:\n51 switch(LOWORD(wParam))\n52 {\n53 case IDC_BTN_ABOUT:\n54 EndDialog(hwndDlg, 0);\n55 return TRUE;\n56 }\n57 }\n58 return FALSE;\n59 }\n60 61 //主窗口\n62 BOOL CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)\n63 {\n64 switch(uMsg)\n65 {\n66 case WM_INITDIALOG:\n67 //设置十进制是选中状态\n68 CheckRadioButton(hwndDlg,IDC_RBN_D,IDC_RBN_B,IDC_RBN_D);\n69 id_pre_checked = IDC_RBN_D;\n70 buf = (char *)malloc(20);\n71 72 //打开注册表\n73 RegOpenKeyEx(HKEY_LOCAL_MACHINE,\u0026ldquo;SYSTEM\\\\CurrentControlSet\\\\Control\\\\Terminal Server\\\\Wds\\\\rdpwd\\\\Tds\\\\tcp\u0026rdquo;,0,KEY_ALL_ACCESS,\u0026amp;wds);\n74 RegOpenKeyEx(HKEY_LOCAL_MACHINE,\u0026ldquo;SYSTEM\\\\CurrentControlSet\\\\Control\\\\Terminal Server\\\\WinStations\\\\RDP-Tcp\u0026rdquo;,0,KEY_ALL_ACCESS,\u0026amp;winstations);\n75 76 //读取注册表的值\n77 DWORD value = 0;\n78 DWORD dwType = REG_DWORD;\n79 DWORD dwSize = sizeof(DWORD);\n80 RegQueryValueEx(wds,\u0026ldquo;PortNumber\u0026rdquo;,NULL,\u0026amp;dwType,(LPBYTE)\u0026amp;value, \u0026amp;dwSize);\n81 82 SetDlgItemText(hwndDlg,IDC_EDT,DtoDstr(value));\n83 84 //加载图标\n85 hIcon = LoadIcon(hInst,(LPCTSTR)ICON_LOH);\n86 SendMessage(hwndDlg,WM_SETICON,ICON_BIG,(LPARAM)hIcon);\n87 //SendMessage(hwndDlg,WM_SETICON,ICON_SMALL,(LPARAM)hIcon);\n88 return TRUE;\n89 90 case WM_CLOSE:\n91 EndDialog(hwndDlg, 0);\n92 RegCloseKey(wds);\n93 RegCloseKey(winstations);\n94 return TRUE;\n95 96 case WM_COMMAND:\n97 switch(LOWORD(wParam))\n98 {\n99 case IDC_BTN_Q:\n100 EndDialog(hwndDlg, 0);\n101 return TRUE;\n102 103 case IDC_BTN_E:\n104 memset(buf,0,20);\n105 GetDlgItemText(hwndDlg,IDC_EDT,buf,19);\n106 port = GetPortNumber(buf,id_pre_checked);\n107 if(RegSetValueEx(wds,\u0026ldquo;PortNumber\u0026rdquo;,0,REG_DWORD,(LPBYTE)\u0026amp;port, sizeof(DWORD)) || RegSetValueEx(winstations,\u0026ldquo;PortNumber\u0026rdquo;,0,REG_DWORD,(LPBYTE)\u0026amp;port, sizeof(DWORD)))\n108 {\n109 printf(\u0026ldquo;Could not set the event message file.\u0026rdquo;);\n110 }\n111 return TRUE;\n112 113 case IDC_BTN_B:\n114 port = 3389;\n115 if(RegSetValueEx(wds,\u0026ldquo;PortNumber\u0026rdquo;,0,REG_DWORD,(LPBYTE)\u0026amp;port, sizeof(DWORD)) || RegSetValueEx(winstations,\u0026ldquo;PortNumber\u0026rdquo;,0,REG_DWORD,(LPBYTE)\u0026amp;port, sizeof(DWORD)))\n116 {\n117 printf(\u0026ldquo;Could not set the event message file.\u0026rdquo;);\n118 }\n119 SetDlgItemText(hwndDlg,IDC_EDT,DtoDstr(port));\n120 return TRUE;\n121 122 case IDC_BTN_A:\n123 //MessageBox(hwndDlg, \u0026ldquo;关于!\u0026rdquo;, \u0026ldquo;提示信息\u0026rdquo;, MB_ICONINFORMATION);\n124 return DialogBox(hInst, MAKEINTRESOURCE(IDD_DLG_ABOUT), hwndDlg, AboutDialogProc);\n125 //return TRUE;\n126 127 case IDC_RBN_D:\n128 memset(buf,0,20);\n129 GetDlgItemText(hwndDlg,IDC_EDT,buf,19);\n130 if(id_pre_checked == IDC_RBN_H)\n131 {\n132 SetDlgItemText(hwndDlg, IDC_EDT, HtoD(buf));\n133 }\n134 if(id_pre_checked == IDC_RBN_B)\n135 {\n136 SetDlgItemText(hwndDlg,IDC_EDT,BtoD(buf));\n137 }\n138 139 id_pre_checked = IDC_RBN_D;\n140 return TRUE;\n141 142 case IDC_RBN_H:\n143 memset(buf,0,20);\n144 GetDlgItemText(hwndDlg,IDC_EDT,buf,19);\n145 if(id_pre_checked == IDC_RBN_D)\n146 {\n147 SetDlgItemText(hwndDlg, IDC_EDT, DtoH(buf));\n148 }\n149 if(id_pre_checked == IDC_RBN_B)\n150 {\n151 SetDlgItemText(hwndDlg,IDC_EDT,BtoH(buf));\n152 }\n153 id_pre_checked = IDC_RBN_H;\n154 return TRUE;\n155 156 case IDC_RBN_B:\n157 memset(buf,0,20);\n158 GetDlgItemText(hwndDlg,IDC_EDT,buf,19);\n159 if(id_pre_checked == IDC_RBN_H)\n160 {\n161 SetDlgItemText(hwndDlg, IDC_EDT, HtoB(buf));\n162 }\n163 if(id_pre_checked == IDC_RBN_D)\n164 {\n165 SetDlgItemText(hwndDlg,IDC_EDT,DtoB(buf));\n166 }\n167 id_pre_checked = IDC_RBN_B;\n168 return TRUE;\n169 }\n170 }\n171 return FALSE;\n172 }\n173 174 175 int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)\n176 {\n177 hInst = hInstance;\n178 179 // The user interface is a modal dialog box\n180 return DialogBox(hInstance, MAKEINTRESOURCE(DLG_MAIN), NULL, DialogProc);\n181 }\n182 关于使用windows api操作注册表需要注意的是：\n1.先打开注册表，获得注册表的句柄，代码为:\n1 //打开注册表\n2 RegOpenKeyEx(HKEY_LOCAL_MACHINE,\u0026ldquo;SYSTEM\\\\CurrentControlSet\\\\Control\\\\Terminal Server\\\\Wds\\\\rdpwd\\\\Tds\\\\tcp\u0026rdquo;,0,KEY_ALL_ACCESS,\u0026amp;wds);\n3 RegOpenKeyEx(HKEY_LOCAL_MACHINE,\u0026ldquo;SYSTEM\\\\CurrentControlSet\\\\Control\\\\Terminal Server\\\\WinStations\\\\RDP-Tcp\u0026rdquo;,0,KEY_ALL_ACCESS,\u0026amp;winstations);\n2.然后在读入注册表的值或是写入注册表的值\n例如读取PortNumber的值到变量value中：\n//读取注册表的值\nDWORD value = 0;\nDWORD dwType = REG_DWORD;\nDWORD dwSize = sizeof(DWORD);\nRegQueryValueEx(wds,\u0026ldquo;PortNumber\u0026rdquo;,NULL,\u0026amp;dwType,(LPBYTE)\u0026amp;value, \u0026amp;dwSize);\n例如写值到PortNumber中：\nint port = 3389；\nRegSetValueEx(wds,\u0026ldquo;PortNumber\u0026rdquo;,0,REG_DWORD,(LPBYTE)\u0026amp;port, sizeof(DWORD))\n这里是最终程序的截图，[下载这个程序]\n","permalink":"https://lewang.dev/posts/2008-12-02-remotedesktop/","summary":"办公室通过路由器上网，所以不能直接远程桌面登录到自己计算机上，办公室的人都想在宿舍登录到自己的计算机上工作学习，所以可以简单的将路由器的3389端口(windows远程桌面默认使用3389端口)映射到自己的IP上，同时修改其他人计算机的远程桌面端口，然后再相应的将路由器端口映射到自己的IP上即可。\n修改远程桌面端口需要修改注册表中的两个值：\n[HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\Terminal Server\\WinStations\\RDP-Tcp]\n\u0026ldquo;PortNumber\u0026rdquo;=dword:00000d3d\n[HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\Terminal Server\\Wds\\rdpwd\\Tds\\tcp]\n\u0026ldquo;PortNumber\u0026rdquo;=dword:00000d3d\n修改两个值，十进制是0-65535，双字十六进制是0x00000000-0x0000FFFF，推荐修改为1024以上。\n如何使用windows api编写一个小程序来修改端口呢？接下来就详细的讲讲这个程序应该如何实现，另外在讨论一下如何使用windows api编写简单的windows程序。\n下载开源的windows api集成开发环境Code::Blocks (访问Code::Blocks主页)\n建议下载集成mingw的CodeBlocks，这样就不用单独下载安装mingw了，如果不知道什么是mingw，赶紧google一下看看吧。配置好开发环境后，就可以开始下一步啦。\n启动Code::Blocks，然后新建一个Win32 GUI Project，这样IDE会自动构建一个对话框，而且程序的基本结构也就构造好了。程序的入口就是WinMain函数，然后就可以在这个基础之上编写我们的程序了。\n先确定这个程序有几个窗口，然后修改resource.rc文件，资料都可以去MSDN查找\nresource.rc\n1 #include \u0026ldquo;resource.h\u0026rdquo;\n2 3 DLG_MAIN DIALOGEX 6,6,269,131\n4 STYLE 0x90CC0800\n5 CAPTION \u0026ldquo;远程桌面连接端口修改器\u0026rdquo;\n6 FONT 8,\u0026ldquo;MS Sans Serif\u0026rdquo;,0,0\n7 BEGIN\n8 CONTROL \u0026ldquo;\u0026quot;,IDC_EDT,\u0026ldquo;Edit\u0026rdquo;,0x50010000,72,51,170,13,0x00000200\n9 CONTROL \u0026ldquo;十进制(\u0026amp;D)\u0026quot;,IDC_RBN_D,\u0026ldquo;Button\u0026rdquo;,0x50010009,20,81,54,9\n10 CONTROL \u0026ldquo;十六进制(\u0026amp;H)\u0026quot;,IDC_RBN_H,\u0026ldquo;Button\u0026rdquo;,0x50010009,90,81,60,9\n11 CONTROL \u0026ldquo;二进制(\u0026amp;B)\u0026quot;,IDC_RBN_B,\u0026ldquo;Button\u0026rdquo;,0x50010009,164,81,54,9\n12 CONTROL \u0026ldquo;修改(\u0026amp;E)\u0026quot;,IDC_BTN_E,\u0026ldquo;Button\u0026rdquo;,0x50010000,14,107,54,13\n13 CONTROL \u0026ldquo;还原(\u0026amp;B)\u0026quot;,IDC_BTN_B,\u0026ldquo;Button\u0026rdquo;,0x50010000,70,107,54,13\n14 CONTROL \u0026ldquo;关于(\u0026amp;A)\u0026quot;,IDC_BTN_A,\u0026ldquo;Button\u0026rdquo;,0x50010000,126,107,54,13\n15 CONTROL \u0026ldquo;退出(\u0026amp;Q)\u0026quot;,IDC_BTN_Q,\u0026ldquo;Button\u0026rdquo;,0x50010000,198,107,54,13\n16 CONTROL \u0026ldquo;远程桌面端口\u0026rdquo;,IDC_STC,\u0026ldquo;Static\u0026rdquo;,0x50000000,16,53,52,9\n17 CONTROL \u0026ldquo;基数\u0026rdquo;,IDC_GRP,\u0026ldquo;Button\u0026rdquo;,0x50000007,14,70,228,24","title":"使用Code Blocks(mingw)IDE利用windows api编写远程桌面端口修改程序"},{"content":"在windows下安装过很多次apache+php+mysql了，每次都没有做一个详细的记录。今天心情不好，所以写个日志。\n这里的安装方法都是参考于php的官方安装文档，所以直接查看它的官方文档就可以安装了\n主要参考这两个手册：php-5中文手册和php-5.2.5-Win32.zip 软件包中的install.txt\n首先请准备以下软件(ps：我的这些软件都是很久前下载的，现在已经有新版本了，不过安装方法都是一样的)\n点击下载apache\n点击下载php-5\n点击下载mysql\n下面开始安装(安装顺序无所谓)和配置，不要一味的照搬，自己结合自己的情况看看\n===\u0026gt; 软件的安装\n1)安装apache_2.2.8-win32-x86-openssl-0.9.8g.msi\n=\u0026gt;安装方法：直接运行安装程序安装\n2）安装php-5\n=\u0026gt; 解压下载的安装包，将解压后的文件目录名改为php，以方便后面的配置\n=\u0026gt;进入刚刚改名为php的目录，找到php.ini-recommended文件，将该文件改名为php.ini\n=\u0026gt;将名为php的目录整个拷贝了任意一个盘(c，d……)的根目录下，以方便后面的配置,我将php整个目录拷贝到了D盘根目录下，即d:\\php\n3）安装mysql\n=\u0026gt;安装方法：直接运行安装程序安装\n=\u0026gt;配置mysql：安装完mysql后，程序会自动弹出配置窗口，直接点击\u0026quot;next\u0026quot;\u0026hellip;\u0026hellip;.，然后设置好相应的字符编码和root用户的密码等\n这样程序就安装完成了，下面分析这些软件应该如何配置才能工作。\n===\u0026gt; 服务器的配置(这里只是简单讲讲如何让服务器正常工作，想要知道更多的配置，请自行查看软件配置文档)\n1）配置php(先前我将解压后的php包整个拷贝到了d目录下d:\\php)\n=\u0026gt;进入php所在目录，目录结构基本是这个样子\n点击展开\nd:\\php\n|\n+\u0026ndash;dev\n| |\n| |-php5ts.lib\n|\n+\u0026ndash;ext \u0026ndash; extension DLLs for PHP\n| |\n| |-php_bz2.dll\n| |\n| |-php_cpdf.dll\n| |\n| |-..\n|\n+\u0026ndash;extras\n| |\n| +\u0026ndash;mibs \u0026ndash; support files for SNMP\n| |\n| +\u0026ndash;openssl \u0026ndash; support files for Openssl\n| |\n| +\u0026ndash;pdf-related \u0026ndash; support files for PDF\n| |\n| |-mime.magic\n|\n+\u0026ndash;pear \u0026ndash; initial copy of PEAR\n|\n|\n|-go-pear.bat \u0026ndash; PEAR setup script\n|\n|-fdftk.dll\n|\n|-..\n|\n|-php-cgi.exe \u0026ndash; CGI executable\n|\n|-php-win.exe \u0026ndash; executes scripts without an opened command prompt\n|\n|-php.exe \u0026ndash; CLI executable - ONLY for command line scripting\n|\n|-..\n|\n|-php.ini-dist \u0026ndash; default php.ini settings\n|\n|-php.ini-recommended \u0026ndash; recommended php.ini settings\n|\n|-php5activescript.dll\n|\n|-php5apache.dll\n|\n|-php5apache2.dll\n|\n|-..\n|\n|-php5ts.dll \u0026ndash; core PHP DLL\n|\n|- 编辑php.ini文件\n找到extension_dir一行，修改为extension_dir = \u0026ldquo;d:\\php\\ext\u0026rdquo;，如果这一句前面有分号，请去掉分号\n找到include_path一行，修改为include_path = \u0026ldquo;.;d:\\php\\PEAR\u0026rdquo;，如果这一句前面有分号，请去掉分号\n找到;extension=php_mysql.dll一行，把前面的分号去掉，就可以连mysql数据库了\n添加环境变量(不知道怎么添加的话，自己google下，或者“右击我的电脑=\u0026gt;属性=\u0026gt;高级=环境变量”)：\n新建变量phprc，值为D:\\php(不知道这一步是不是必须做，不过配置文档里写到了)，在path变量里添加D:\\php，用分号分割开\n2）配置apache\n=\u0026gt;在apache的安装目录下找到httpd.conf文件，这个就是apache的配置文件，在程序的开始菜单里也能找到。\n在文件尾部添加以下代码\nPHPIniDir \u0026ldquo;d:/php\u0026rdquo;\nLoadModule php5_module d:/php/php5apache2_2.dll\nAddType application/x-httpd-php .php\n找到此段代码\n点击展开\nDirectoryIndex index.html\n修改为\n点击展开\nDirectoryIndex index.html index.php index.htm\n这样基本上就配置完成了\n在apache安装目录下找到htdocs目录，修改其中index.html为index.php，修改内容为\n点击展开\nIt works!\n\u003c?php ![](/Images/OutliningIndicators/None.gif) phpinfo(); ![](/Images/OutliningIndicators/None.gif) ?\u003e\n重启apache，或者重启电脑后，在浏览器中输入http://127.0.0.1/即可查看结果\n","permalink":"https://lewang.dev/posts/2008-09-03-226778/","summary":"在windows下安装过很多次apache+php+mysql了，每次都没有做一个详细的记录。今天心情不好，所以写个日志。\n这里的安装方法都是参考于php的官方安装文档，所以直接查看它的官方文档就可以安装了\n主要参考这两个手册：php-5中文手册和php-5.2.5-Win32.zip 软件包中的install.txt\n首先请准备以下软件(ps：我的这些软件都是很久前下载的，现在已经有新版本了，不过安装方法都是一样的)\n点击下载apache\n点击下载php-5\n点击下载mysql\n下面开始安装(安装顺序无所谓)和配置，不要一味的照搬，自己结合自己的情况看看\n===\u0026gt; 软件的安装\n1)安装apache_2.2.8-win32-x86-openssl-0.9.8g.msi\n=\u0026gt;安装方法：直接运行安装程序安装\n2）安装php-5\n=\u0026gt; 解压下载的安装包，将解压后的文件目录名改为php，以方便后面的配置\n=\u0026gt;进入刚刚改名为php的目录，找到php.ini-recommended文件，将该文件改名为php.ini\n=\u0026gt;将名为php的目录整个拷贝了任意一个盘(c，d……)的根目录下，以方便后面的配置,我将php整个目录拷贝到了D盘根目录下，即d:\\php\n3）安装mysql\n=\u0026gt;安装方法：直接运行安装程序安装\n=\u0026gt;配置mysql：安装完mysql后，程序会自动弹出配置窗口，直接点击\u0026quot;next\u0026quot;\u0026hellip;\u0026hellip;.，然后设置好相应的字符编码和root用户的密码等\n这样程序就安装完成了，下面分析这些软件应该如何配置才能工作。\n===\u0026gt; 服务器的配置(这里只是简单讲讲如何让服务器正常工作，想要知道更多的配置，请自行查看软件配置文档)\n1）配置php(先前我将解压后的php包整个拷贝到了d目录下d:\\php)\n=\u0026gt;进入php所在目录，目录结构基本是这个样子\n点击展开\nd:\\php\n|\n+\u0026ndash;dev\n| |\n| |-php5ts.lib\n|\n+\u0026ndash;ext \u0026ndash; extension DLLs for PHP\n| |\n| |-php_bz2.dll\n| |\n| |-php_cpdf.dll\n| |\n| |-..\n|\n+\u0026ndash;extras\n| |\n| +\u0026ndash;mibs \u0026ndash; support files for SNMP\n| |\n| +\u0026ndash;openssl \u0026ndash; support files for Openssl","title":"windows xp sp3 + apache_2.2.8-win32-x86-openssl-0.9.8g.msi + php-5.2.5-Win32.zip + mysql-5.0.45-win32.exe的安装记录"},{"content":"在JavaScript中使用正则表达式来限制输入框——输入框中只能输入数字\n点击查看\n1 这样使用：\n点击查看\n1 请在这里测试\n","permalink":"https://lewang.dev/posts/2008-09-02-226416/","summary":"在JavaScript中使用正则表达式来限制输入框——输入框中只能输入数字\n点击查看\n1 这样使用：\n点击查看\n1 请在这里测试","title":"使用正则表达式来限制输入框中只能输入数字"},{"content":"编写此文的目的：当你看到我这篇博文的时候，估计你就不能体会我为什么要编写此文了。在此文发表之前，我需要一篇博文来改变我的首页的丑陋的状态，于是我就写了这篇博文。\n使用到的工具：大脑一个,手两只， IE Developer(点击下载)，其他东西若干\n打造过程:\n前天申请的BlogJava,感觉很不错，看看后台，发现可以通过修改CSS改变自己博客的面貌。 cnblog上有人说有比较好的costom的模板，可在BlogJava里我却没有找到这个模板。系统里已经自带了很多模板了，都很漂亮，但是自己还是喜欢那种oracle数据库帮助文档那种样式的页面，在后台选了个最接近自己想法的模板cogitation,然后就开始这次改造工程。\n大侠们见谅，以下部分没有什么内涵，都是一些干完体力活后发的牢骚\n如果你还想继续往下看，请确保你已经成功安装了IE Developer浏览器插件。\n打开自己的BlogJava首页，可以看到自己选的模板已经生效了，先用IE Developer查看一下自己首页的结构吧，很简单，一目了然。想起了后台可以自己用HTML定义页面的头部和尾部，这个太好了，直接把原先的页面头和尾设为不显示,css代码如下：\n#footer{ }{\ndisplay:none;\n}\n#top { }{\ndisplay:none;\n}\n然后自己写个页面头和尾，把自己的写的头和尾的html代码拷到后台对应的地方，代码不要是完整的html页，一两个table即可。自己写的头和尾页可以有css，把这些css放到『通过css定制页面』中去吧。例如我的页首页脚html代码就是这样：\nCanvas\n积极且具有忧患意识的人,在每一次忧患中都会看到机会,并且在每个机会中也都会看到某种忧患! ![](/Images/OutliningIndicators/None.gif) ![](/Images/OutliningIndicators/None.gif) ![](/Images/OutliningIndicators/None.gif) ![](/Images/OutliningIndicators/None.gif) ![](/Images/OutliningIndicators/None.gif) ![](/Images/OutliningIndicators/None.gif) ![](/Images/OutliningIndicators/None.gif) ![](/Images/OutliningIndicators/None.gif) ![](/Images/OutliningIndicators/None.gif) ![](/Images/OutliningIndicators/None.gif) 首页 ![](/Images/OutliningIndicators/None.gif) 新随笔 ![](/Images/OutliningIndicators/None.gif) 聚合 ![](/Images/OutliningIndicators/None.gif) 联系 ![](/Images/OutliningIndicators/None.gif) 管理 ![](/Images/OutliningIndicators/None.gif) ![](/Images/OutliningIndicators/None.gif) ![](/Images/OutliningIndicators/None.gif) ![](/Images/OutliningIndicators/None.gif) ![](/Images/OutliningIndicators/None.gif) ![](/Images/OutliningIndicators/None.gif) ![](/Images/OutliningIndicators/None.gif) ![](/Images/OutliningIndicators/None.gif) Copyright © Canvas Wong Contact me\n接下来就是用IE develop导出页面css，然后自己修改成自己想要的样子，再放回『通过css定制页面』中去即可。\n看到留言框内有个google广告，通过后台可以去掉这个广告，但是会留下一个iframe框，不知道你那是不是也这样。仔细看了一下留言页面代码，使用这段css代码把iframe设置为不显示:\niframe{ }{\ndisplay:none;\n}\n总结:右键-\u0026gt;查看源代码,原模板的东西都在，只是没有显示出来，糊弄一下看客的眼睛而已。\n找一个自己喜欢的模板，将它打造得面目全非吧。\n","permalink":"https://lewang.dev/posts/2008-05-06-template-canvas/","summary":"编写此文的目的：当你看到我这篇博文的时候，估计你就不能体会我为什么要编写此文了。在此文发表之前，我需要一篇博文来改变我的首页的丑陋的状态，于是我就写了这篇博文。\n使用到的工具：大脑一个,手两只， IE Developer(点击下载)，其他东西若干\n打造过程:\n前天申请的BlogJava,感觉很不错，看看后台，发现可以通过修改CSS改变自己博客的面貌。 cnblog上有人说有比较好的costom的模板，可在BlogJava里我却没有找到这个模板。系统里已经自带了很多模板了，都很漂亮，但是自己还是喜欢那种oracle数据库帮助文档那种样式的页面，在后台选了个最接近自己想法的模板cogitation,然后就开始这次改造工程。\n大侠们见谅，以下部分没有什么内涵，都是一些干完体力活后发的牢骚\n如果你还想继续往下看，请确保你已经成功安装了IE Developer浏览器插件。\n打开自己的BlogJava首页，可以看到自己选的模板已经生效了，先用IE Developer查看一下自己首页的结构吧，很简单，一目了然。想起了后台可以自己用HTML定义页面的头部和尾部，这个太好了，直接把原先的页面头和尾设为不显示,css代码如下：\n#footer{ }{\ndisplay:none;\n}\n#top { }{\ndisplay:none;\n}\n然后自己写个页面头和尾，把自己的写的头和尾的html代码拷到后台对应的地方，代码不要是完整的html页，一两个table即可。自己写的头和尾页可以有css，把这些css放到『通过css定制页面』中去吧。例如我的页首页脚html代码就是这样：\nCanvas\n积极且具有忧患意识的人,在每一次忧患中都会看到机会,并且在每个机会中也都会看到某种忧患! ![](/Images/OutliningIndicators/None.gif) ![](/Images/OutliningIndicators/None.gif) ![](/Images/OutliningIndicators/None.gif) ![](/Images/OutliningIndicators/None.gif) ![](/Images/OutliningIndicators/None.gif) ![](/Images/OutliningIndicators/None.gif) ![](/Images/OutliningIndicators/None.gif) ![](/Images/OutliningIndicators/None.gif) ![](/Images/OutliningIndicators/None.gif) ![](/Images/OutliningIndicators/None.gif) 首页 ![](/Images/OutliningIndicators/None.gif) 新随笔 ![](/Images/OutliningIndicators/None.gif) 聚合 ![](/Images/OutliningIndicators/None.gif) 联系 ![](/Images/OutliningIndicators/None.gif) 管理 ![](/Images/OutliningIndicators/None.gif) ![](/Images/OutliningIndicators/None.gif) ![](/Images/OutliningIndicators/None.gif) ![](/Images/OutliningIndicators/None.gif) ![](/Images/OutliningIndicators/None.gif) ![](/Images/OutliningIndicators/None.gif) ![](/Images/OutliningIndicators/None.gif) ![](/Images/OutliningIndicators/None.gif) Copyright © Canvas Wong Contact me\n接下来就是用IE develop导出页面css，然后自己修改成自己想要的样子，再放回『通过css定制页面』中去即可。\n看到留言框内有个google广告，通过后台可以去掉这个广告，但是会留下一个iframe框，不知道你那是不是也这样。仔细看了一下留言页面代码，使用这段css代码把iframe设置为不显示:\niframe{ }{\ndisplay:none;\n}\n总结:右键-\u0026gt;查看源代码,原模板的东西都在，只是没有显示出来，糊弄一下看客的眼睛而已。\n找一个自己喜欢的模板，将它打造得面目全非吧。","title":"也扯打造个性BlogJava模板"},{"content":"声明:ICTCLAS以及我修改ICTCLAS JNI For Java的版权均归原作者所有，未经原作者允许，请勿作其它商业用途\n首先下载ICTCLAS Beta版本公开源代码:点击下载\n我使用的是Visual studio 2005开发环境，源码使用VC6开发的，现在把这个工程转移到VS2005下。我使用的方法比较笨，基本上是属于体力+暴力型的，不过自己觉得爽就可以了\n在VS2005里新建Win32项目FREEICTCLAS,在application Settings选项里选中Dll和Empty Project,然后新建以下过滤器(New Filter)，并且将原项目的源码及其目录结构copy到现在的win32项目的目录下(过滤器和项目目录示意图)\n示意图(点击前面+号展开)\n| |\n这样这个win32工程基本就建完了。\n观察ICTCLAS源代码后编写以下带有native声明的方法的java类，只实现了头两个方法，第三个暂未实现，与ICTCLAS接口基本保持一致\n代码如下:\nFreeICTCLAS.java(点击+展开)\npublic class FreeICTCLAS\n{\n///*\n* 初始化设置取值都是0,1,2\n*/\npublic native boolean FreeICTCLAS_Init(int OperateType, int OutputFormat);\n///*\n* 与Free版本中有所不同,返回值为结果\n/\npublic native String FreeICTCLAS_ParagraphProcess(String sParagraph);\n/**//\n* 与Free版本中基本保持一致\n*/\npublic native boolean FreeICTCLAS_FileProcess(String sSourceFilename, String sResultFilename);\npublic static void main(String[] args)\n{\nSystem.loadLibrary(\u0026ldquo;FreeICTCLAS\u0026rdquo;);\nFreeICTCLAS test = new FreeICTCLAS();\ntest.FreeICTCLAS_Init(0,0);\nString str = test.FreeICTCLAS_ParagraphProcess(\u0026ldquo;与Free版本中有所不同,返回值为结果，和平奥运，绿色奥运，今天天气很是不错啊\u0026rdquo;);\nSystem.out.println(str);\n}\n}\n然后在命令行输入Javac FreeICTCLAS.java编译FreeICTCLAS.java\n生成FreeICTCLAS.class文件后，再使用javah FreeICTCLAS生成FreeICTCLAS.h，代码如下：\n///* DO NOT EDIT THIS FILE - it is machine generated */\n#include \u0026lt;jni.h\u0026gt;\n///* Header for class FreeICTCLAS /\n#ifndef _Included_FreeICTCLAS\n#define _Included_FreeICTCLAS\n#ifdef __cplusplus\nextern \u0026ldquo;C\u0026rdquo; {\n#endif\n/**//\n* Class: FreeICTCLAS\n* Method: FreeICTCLAS_Init\n* Signature: (II)Z\n*/\nJNIEXPORT jboolean JNICALL Java_FreeICTCLAS_FreeICTCLAS_1Init\n(JNIEnv , jobject, jint, jint);\n/**//\n* Class: FreeICTCLAS\n* Method: FreeICTCLAS_ParagraphProcess\n* Signature: (Ljava/lang/String;)Ljava/lang/String;\n*/\nJNIEXPORT jstring JNICALL Java_FreeICTCLAS_FreeICTCLAS_1ParagraphProcess\n(JNIEnv , jobject, jstring);\n/**//\n* Class: FreeICTCLAS\n* Method: FreeICTCLAS_FileProcess\n* Signature: (Ljava/lang/String;Ljava/lang/String;)Z\n*/\nJNIEXPORT jboolean JNICALL Java_FreeICTCLAS_FreeICTCLAS_1FileProcess\n(JNIEnv *, jobject, jstring, jstring);\n#ifdef __cplusplus\n}\n#endif\n#endif\n将FreeICTCLAS.h拷贝到win32项目所在目录中，再将其添加到项目中，接下来就是编写FreeICTCLAS.cpp实现头文件里方法\n主要代码如下：\n#include \u0026ldquo;FreeICTCLAS.h\u0026rdquo;\n#include \u0026ldquo;util.h\u0026rdquo;\n#include \u0026ldquo;globeVar.h\u0026rdquo;\n#include \u0026lt;string.h\u0026gt;\n#include \u0026lt;stdlib.h\u0026gt;\n///*\n* Class: FreeICTCLAS\n* Method: FreeICTCLAS_Init\n* Signature: (II)Z\n*/\nJNIEXPORT jboolean JNICALL Java_FreeICTCLAS_FreeICTCLAS_1Init\n(JNIEnv *env, jobject obj, jint OperateType, jint OutputFormat)\n{\nm_ICTCLAS.m_nOperateType = OperateType;\nm_ICTCLAS.m_nOutputFormat = OutputFormat;\nreturn JNI_TRUE;\n}\n///*\n* Class: FreeICTCLAS\n* Method: FreeICTCLAS_ParagraphProcess\n* Signature: (Ljava/lang/String;)Ljava/lang/String;\n*/\nJNIEXPORT jstring JNICALL Java_FreeICTCLAS_FreeICTCLAS_1ParagraphProcess\n(JNIEnv env, jobject obj, jstring sParagraph)\n{\nchar sResult,sSource;\nsSource = jstringTochar(env, sParagraph);\nif(m_ICTCLAS.m_nOutputFormat!=2)\nsResult=new char [(strlen(sSource)+13)3];\nelse\nsResult=new char [(strlen(sSource)+13)50];\nif(!m_ICTCLAS.ParagraphProcessing(sSource,sResult))\nexit(1);\nelse\nreturn chartoJstring(env,sResult);\n}\n/**//\nClass: FreeICTCLAS\nMethod: FreeICTCLAS_FileProcess\nSignature: (Ljava/lang/String;Ljava/lang/String;)Z\n/\nJNIEXPORT jboolean JNICALL Java_FreeICTCLAS_FreeICTCLAS_1FileProcess\n(JNIEnv *env, jobject obj, jstring sSourceFilename, jstring sResultFilename)\n{\nreturn JNI_TRUE;\n}\n编译项目，在release目录中找到FreeICTCLAS.dll，将其拷贝到FreeICTCLAS.class所在的目录中\n现在运行java FreeICTCLAS即可看到中文分词的结果啦\n怎么把ICTCLAS JNI for Java添加到eclipse项目中去?下次写吧。\n当你看到这句话的时候，你可以直接发邮件向我索要win32项目和eclipse项目\nBlogJava后台编辑器不好用，^_^\n","permalink":"https://lewang.dev/posts/2008-05-06-jni-free-ictclas/","summary":"声明:ICTCLAS以及我修改ICTCLAS JNI For Java的版权均归原作者所有，未经原作者允许，请勿作其它商业用途\n首先下载ICTCLAS Beta版本公开源代码:点击下载\n我使用的是Visual studio 2005开发环境，源码使用VC6开发的，现在把这个工程转移到VS2005下。我使用的方法比较笨，基本上是属于体力+暴力型的，不过自己觉得爽就可以了\n在VS2005里新建Win32项目FREEICTCLAS,在application Settings选项里选中Dll和Empty Project,然后新建以下过滤器(New Filter)，并且将原项目的源码及其目录结构copy到现在的win32项目的目录下(过滤器和项目目录示意图)\n示意图(点击前面+号展开)\n| |\n这样这个win32工程基本就建完了。\n观察ICTCLAS源代码后编写以下带有native声明的方法的java类，只实现了头两个方法，第三个暂未实现，与ICTCLAS接口基本保持一致\n代码如下:\nFreeICTCLAS.java(点击+展开)\npublic class FreeICTCLAS\n{\n///*\n* 初始化设置取值都是0,1,2\n*/\npublic native boolean FreeICTCLAS_Init(int OperateType, int OutputFormat);\n///*\n* 与Free版本中有所不同,返回值为结果\n/\npublic native String FreeICTCLAS_ParagraphProcess(String sParagraph);\n/**//\n* 与Free版本中基本保持一致\n*/\npublic native boolean FreeICTCLAS_FileProcess(String sSourceFilename, String sResultFilename);\npublic static void main(String[] args)\n{\nSystem.loadLibrary(\u0026ldquo;FreeICTCLAS\u0026rdquo;);\nFreeICTCLAS test = new FreeICTCLAS();\ntest.FreeICTCLAS_Init(0,0);\nString str = test.","title":"打造ICTCLAS JNI For Java"},{"content":"毕业设计是做一个中文的XML信息检索系统。当时选题的时候真的是不知道天高地厚，选了这么一个难题，但是毕竟已经选了，那就不要畏缩了！查阅了信息检索的相关资料后，遇到了一个很棘手的问题——中文如何分词？这是个大问题，不知道如何入手。于是查查资料看看有没有现成中文分词系统拿来用，这一找就找到了中科院的ICTCLAS。网上已经有很多的已经封装好了的ICTCLAS.dll，由于毕设打算用Java技术打造一个系统原型，那就需要通过JNI技术来调用ICTCLAS.dll。其实这也已经有人做好了，但是，我还是想知道JNI的个什么样的东西，还想知道如何正确的使用这个技术。\n已经习惯了从Hello World开始。\n我使用的是JDK_1.6.0和Visual studio 2005，使用Visual studio 2005生成可供Java调用的dll需要做以下设置：\n在Visual studio 2005的菜单Tools\\options\\Projects and Solutions\\VC++ Directories，添加JDK安装目录下inlcude和include/win32目录,如下图所示\n设置图(点击前面+号展开)\n编写带有native声明的方法的java类，代码如下\nJniTest.java(点击前面+号展开)\npublic class JniTest{ /*静态块装载JniTest.dll，不用写出后缀名.dll,因为不同平台，系统会自动识别*/ static { System.loadLibrary(\u0026quot;JniTest\u0026quot;); } /*native方法申明,不要写具体的实现*/ public native void sayHello(); } 然后在命令行输入Javac JniTest.java编译JniTest.java\n生成JniTest.class文件后，再使用javah JniTest生成JniTest.h，代码如下\nJniTest.h(点击前面+号展开)\n/* DO NOT EDIT THIS FILE - it is machine generated */ #include \u0026lt;jni.h\u0026gt; /* Header for class JniTest */ #ifndef _Included_JniTest #define _Included_JniTest #ifdef __cplusplus extern \u0026quot;C\u0026quot; { #endif /* * Class: JniTest * Method: sayHello * Signature: ()V */ JNIEXPORT void JNICALL Java_JniTest_sayHello (JNIEnv *, jobject); #ifdef __cplusplus } #endif #endif 在Visual studio 2005里新建项目Win32 project，在弹出的对话框中点击application setting，然后再选上dll和Empty project。 接着在项目中添加JniTest.h和JniTest.cpp，JniTest.cpp代码如下：\nJniTest.cpp(点击前面+号展开)\n#include \u0026quot;JniTest.h\u0026quot; #include\u0026lt;stdio.h\u0026gt; JNIEXPORT void JNICALL Java_JniTest_sayHello (JNIEnv *, jobject) { printf(\u0026quot;Hello World！\\n\u0026quot;); } 编译生成JniTest.dll,在项目下找到它，并拷贝到JniTest.java所在的目录,最后编写测试程序Test.java，如下：\nTest.java(点击前面+号展开)\npublic class Test { public static void main(String[] args){ new JniTest().jniTest.sayHello(); } } 最后终于看到了期待的Hello World!\n","permalink":"https://lewang.dev/posts/2008-05-05-jni-hello-world/","summary":"毕业设计是做一个中文的XML信息检索系统。当时选题的时候真的是不知道天高地厚，选了这么一个难题，但是毕竟已经选了，那就不要畏缩了！查阅了信息检索的相关资料后，遇到了一个很棘手的问题——中文如何分词？这是个大问题，不知道如何入手。于是查查资料看看有没有现成中文分词系统拿来用，这一找就找到了中科院的ICTCLAS。网上已经有很多的已经封装好了的ICTCLAS.dll，由于毕设打算用Java技术打造一个系统原型，那就需要通过JNI技术来调用ICTCLAS.dll。其实这也已经有人做好了，但是，我还是想知道JNI的个什么样的东西，还想知道如何正确的使用这个技术。\n已经习惯了从Hello World开始。\n我使用的是JDK_1.6.0和Visual studio 2005，使用Visual studio 2005生成可供Java调用的dll需要做以下设置：\n在Visual studio 2005的菜单Tools\\options\\Projects and Solutions\\VC++ Directories，添加JDK安装目录下inlcude和include/win32目录,如下图所示\n设置图(点击前面+号展开)\n编写带有native声明的方法的java类，代码如下\nJniTest.java(点击前面+号展开)\npublic class JniTest{ /*静态块装载JniTest.dll，不用写出后缀名.dll,因为不同平台，系统会自动识别*/ static { System.loadLibrary(\u0026quot;JniTest\u0026quot;); } /*native方法申明,不要写具体的实现*/ public native void sayHello(); } 然后在命令行输入Javac JniTest.java编译JniTest.java\n生成JniTest.class文件后，再使用javah JniTest生成JniTest.h，代码如下\nJniTest.h(点击前面+号展开)\n/* DO NOT EDIT THIS FILE - it is machine generated */ #include \u0026lt;jni.h\u0026gt; /* Header for class JniTest */ #ifndef _Included_JniTest #define _Included_JniTest #ifdef __cplusplus extern \u0026quot;C\u0026quot; { #endif /* * Class: JniTest * Method: sayHello * Signature: ()V */ JNIEXPORT void JNICALL Java_JniTest_sayHello (JNIEnv *, jobject); #ifdef __cplusplus } #endif #endif 在Visual studio 2005里新建项目Win32 project，在弹出的对话框中点击application setting，然后再选上dll和Empty project。 接着在项目中添加JniTest.","title":"JNI技术入门之Hello World篇"},{"content":"","permalink":"https://lewang.dev/archives/","summary":"","title":"归档"}]