-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcontent.json
More file actions
1 lines (1 loc) · 80.6 KB
/
content.json
File metadata and controls
1 lines (1 loc) · 80.6 KB
1
[{"title":"置顶-研发与技术管理小结","date":"2016-12-31T17:01:01.000Z","path":"2017/01/pm-tech-sys-summary/","text":"管理小结 研发与技术管理小结方法论,流程,工具 技术文档接口设计 接口设计规范 安全 CentOS 安全加固 Web 服务器与系统安全","tags":[{"name":"code standard","slug":"code-standard","permalink":"http://foudingzheng.info/tags/code-standard/"},{"name":"mgmt","slug":"mgmt","permalink":"http://foudingzheng.info/tags/mgmt/"}]},{"title":"Web 服务器与系统安全","date":"2016-04-28T13:30:35.000Z","path":"2016/04/web-security/","text":"基本原则 最小的权限+最小的服务=最大的安全。保持简单,程序只实现指定的功能。坚持最小权限,把可能造成的危害降到最低。 对操作者(IP)的所有操作默认不信任,采用白名单机制,只放行已知的操作。 永远不要信任用户的输入。对所有的输入进行前台和后台两次检查。 操作之前先备份。 密码复杂度:长度至少8位,并包括数字、小写字母、大写字母和特殊符号4类中至少2类 安全范围 操作系统安全加固 Web 程序安全 数据库安全 接口安全 其他系统服务安全 操作系统安全加固 禁用不使用的用户 关闭不必要的端口和服务 禁止root直接远程登录 防止一般网络攻击: 禁ping等 其他。 CentosOS 加固的教程不少。可参考: HowTos/OS Protection - CentOS Wiki CentOS 6 服务器简单安全配置 - 网站架构技术总结 - 51CTO技术博客 让 Linux 服务器更加安全的经验总结 - Kyle 笔记 Java堂 » CentOS安装之后的系统安全配置 CentOS Linux服务器安全设置 | 系统运维 Web 程序安全常见名词解释 浏览器安全同源策略(Same Origin Policy)防止了跨域读写某些资源。浏览器提供了浏览器沙箱,使进程在一个相对独立的空间运行,能在一定程度上保护浏览器安全。 跨站脚本攻击 XSS跨站脚本攻击主要是注入到网站内容中,授权用户访问内容时执行一段恶意代码,从而获取用户的私密信息或者进行破坏。通常叫做XSS攻击,是针对动态网站的攻击。 跨站点请求伪造 CSRFCSRF,指的是伪造出一个请求,诱使授权用户访问,以授权用户的身份去执行请求,从而达到对授权用户信息的读取、攻击等。 点击劫持Click jacking,是指将恶意代码隐藏在看似无害的内容后者按钮之下,诱导用户访问的一种手段。 常见攻击类型 SQL 注入 跨站请求伪造攻击(CSRF) 跨站脚本攻击(XSS) 远程任意命令执行 拒绝服务攻击(DoS) Cookie劫持 检查项 输入的数据没有进行有效的控制和验证 防恶意注册: 可否用自动填表工具自动注册用户 有无缺省的超级用户 密码错误次数有无限制 cookie 中或 隐藏变量中是否含有用户名\\密码\\userid 等关键信息 通过 http 抓包的获取 http 请求信息包经改装后重现发送 文件上传风险 访问控制策略是否得当 其他 数据库安全 用户:最小权限 数据库中存在的密码应经过加密 其他 接口安全 其他系统服务 redis tomcat 防范措施 审计 使用漏洞扫描工具、入侵检测系统等进行系统渗透测试。 防火墙配置 Web 程序防火墙(WAF): modSecurity 日常维护项目 定期做日志安全检查、审计 及时给操作系统打补丁 其他 参考资料通用 CentOS 6 服务器安全配置指南 - Sean’s Notes - SegmentFault关闭不必要的端口和服务-CentOS | 码农日记","tags":[{"name":"code standard","slug":"code-standard","permalink":"http://foudingzheng.info/tags/code-standard/"},{"name":"security","slug":"security","permalink":"http://foudingzheng.info/tags/security/"}]},{"title":"CentOS 安全加固","date":"2016-04-26T03:00:05.000Z","path":"2016/04/security-centos-harden/","text":"CentOS 加固123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113# 用户名USR=ajhealth# 密码:大小写、特殊字符、数字,大于8个字符authconfig --passalgo=sha512 --update### 移除空闲时间大于15分钟的用户echo \"readonly TMOUT=900\" >> /etc/profile.d/os-security.shecho \"readonly HISTFILE\" >> /etc/profile.d/os-security.shchmod +x /etc/profile.d/os-security.sh### 禁用不使用的用户和组cp /etc/passwd{,.bak}sed -i \"/^adm:x\\|lp:x\\|shutdown:x\\|halt:x\\|uucp:x\\|operator:x\\|games:x\\|gopher:x\\|ftp:x\\|nfsnobody:x\\|nfsnobody:x\\|postfix:x/ s/^/# /\" /etc/passwd cp /etc/group{,.dist}sed -i \"/adm:x\\|lp:x\\|uucp:x\\|games:x\\|gopher:x\\|video:x\\|dip:x\\|ftp:x\\|audio:x\\|floppy:x\\|postfix:x/ s/^/# /\" /etc/group### 关闭不使用的服务chkconfig --list | grep '3:on'...### 创建 sudo 用户visudo### SSH安全设置cp /etc/ssh/sshd_config{,.bak}sed -i \"s/PermitRootLogin yes/PermitRootLogin no/\" /etc/ssh/sshd_config # 禁止root直接远程登录sed -i \"/^#Port 22/a Port 11011\" /etc/ssh/sshd_config # change portsed -i \"/^#IgnoreRhosts/ s/^#//g\" /etc/ssh/sshd_config # ignore Rhosts filesecho \"auth required pam_tally2.so deny=6 unlock_time=180 even_deny_root root_unlock_time=180\" >> /etc/pam.d/login # 限制登录失败次数并锁定#### 登录IP限制##### 更严格的限制是在sshd_config中定死允许ssh的用户和来源ip:echo AllowUsers $USR@10.116.44.24 >> /etc/ssh/sshd_config# echo AllowUsers $USR@10.116.44.24 hadoop@10.116.44.24 hadoop@hadoop1 hadoop@10.116.43.165 >> /etc/ssh/sshd_configtail -2 /etc/ssh/sshd_config# restart sshd:service sshd restart[ 或者使用tcpwrapper: vi /etc/hosts.allow sshd:172.29.73.23 sshd:172.29.73. vi /etc/hosts.deny sshd:all]#### 减少history命令记录sed -i \"$ a \\\\if [ \\\"\\$SHLVL\\\" = 1 ]; then\\\\ history -c\\\\fi\" ~/.bash_logoutsed -i \"$ a \\\\if [ \\\"\\$SHLVL\\\" = 1 ]; then\\\\ history -c\\\\fi\" $(eval echo ~$USER)/.bash_logout#### 禁pingecho 1 > /proc/sys/net/ipv4/icmp_echo_ignore_allecho \"echo 1 > /proc/sys/net/ipv4/icmp_echo_ignore_all\" >> /etc/rc.d/rc.local%% --- ---#### 增强特殊文件权限给下面的文件加上不可更改属性,从而防止非授权用户获得权限chattr +i /etc/passwdchattr +i /etc/shadowchattr +i /etc/groupchattr +i /etc/gshadowchattr +i /etc/services #给系统服务端口列表文件加锁,防止未经许可的删除或添加服务chattr +i /etc/pam.d/suchattr +i /etc/ssh/sshd_config### 显示文件的属性lsattr /etc/passwd /etc/shadow /etc/services /etc/ssh/sshd_config### 注意:执行以上 chattr 权限修改之后,就无法添加删除用户了。### 如果再要添加删除用户,需要先取消上面的设置,等用户添加删除完成之后,再执行上面的操作,例如取消只读权限chattr -i /etc/passwd。(记得重新设置只读)#### 配置只能使用密钥文件登录**注意:以普通账号操作,不是以root账号操作**ssh-keygen -t rsa -b 2048Generating public/private rsa key pair.Enter file in which to save the key (/root/.ssh/id_rsa): //默认路径,回车Enter passphrase (empty for no passphrase): //输入你的密钥短语,登录时使用Enter same passphrase again: cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keyschmod 600 ~/.ssh/authorized_keyscat ~/.ssh/id_rsa# save the content to local, and then:rm ~/.ssh/id_rsa*sed -i \"/#RSAAuthentication\\|#PubkeyAuthentication\\|#AuthorizedKeysFile/ s/^#//\" /etc/ssh/sshd_config sed -i \"s/PasswordAuthentication yes/PasswordAuthentication no/\" /etc/ssh/sshd_config### 防止IP欺骗cat >> /etc/host.conf <<EOForder hosts,bindmulti onnospoof onEOF","tags":[{"name":"code standard","slug":"code-standard","permalink":"http://foudingzheng.info/tags/code-standard/"},{"name":"security","slug":"security","permalink":"http://foudingzheng.info/tags/security/"}]},{"title":"接口设计规范","date":"2016-04-12T03:22:25.000Z","path":"2016/04/interf-design/","text":"概述接口形式主要为 RESTful,但又不拘泥于 RESTful。 一个完整的 API 请求过程包括: HTTP请求方式 URL Endpoint 请求数据 响应结果 下面分述之。 HTTP请求方式 HTTP Operation Similar to SQL Stmt Description GET SELECT 获取,查找 POST CREATE 新增创建 PUT UPDATE 在服务器更新资源(客户端提供改变后的完整资源) PATCH UPDATE 在服务器部分更新资源(客户端提供改变的属性) DELETE UPDATE 删除 URL EndpointURI指向的是唯一的资源对象/资源集合列表。统一所有的 endpoint 使用复数使得 URL 更加规整,让API使用者更加容易理解,对开发者来说也更容易实现。 URL Endpoint 组成一个完整的 URL Endpoint 依次是: 网络协议(HTTP, HTTPS) 服务器地址 版本 接口名称 参数(可选) GET 参数使用Query String 格式:如:查询时,过滤条件使用 Query String,如 ?offset=0&limit=10&sort=_created_at;参数列表应该被 encode 过 用来描述数据或者请求的元数据放Header中,例如 X-Result-Fields 示例: GET https://api.example.com/v1/users/123456/profiles 说明: API 与用户的通信协议,总是使用 HTTPS 协议。 尽量将API部署在专用域名之下:api.example.com 不使用大写字母;使用中线 - 代替下划线 _ 版本控制的另一种做法是,将版本号放在HTTP头信息中,但不如放入URL方便和直观 一些常见的参数。 ?limit=10:指定返回记录的数量 ?offset=10:指定返回记录的开始位置。 ?page=2&per_page=100:指定第几页,以及每页的记录数。 ?sortby=name&order=asc:指定返回结果按照哪个属性排序,以及排序顺序。 ?user_type_id=1:指定筛选条件 接口分类分为对资源对象的CURD操作,服务型接口和系统设置三种。 资源对象的CURD操作GET https://~/$version/trades 获取trades列表 GET https://~/$version/trades/:id 根据id获取单个trade POST https://~/$version/trades 创建trade PUT https://~/$version/trades/:id 根据id更新trade PATCH https://~/$version/trades/:id 根据id部分更新trade DELETE https://~/$version/trades/:id 根据id删除trade 服务型接口使用 services 标识: https://~/services/$version/server-name 示例: GET https://~/services/$version/search?q=filter?category=file 搜索 PUT https://~/services/$version/queued/jobs 往任务队列里面添加一个新的任务 DELETE https://~/services/$version/queued/jobs/:id 根据id删除任务 系统设置使用 settings 标识: https://~/settings/$version/server-name 如:更改界面语言环境 PUT https://~/settings/$version/gui/lang { "lang": "zh-CN" } 请求数据使用 JSON 格式传输数据,在 http 请求头和响应头申明 Content-Type: Request Accept: application/json Content-Type: application/json;charset=UTF-8 { "name": "user1", "location": "Xiamen" } 响应不使用 Envelope: 响应状态(成功/出错)包含在响应头中;返回的数据结构尽可能简单,不过于包装。 成功时格式Response Content-Type: application/json;charset=UTF-8 { "id": 101 "name" : "user1", "location" : "Xiamen" } 出错时处理出错响应包括 返回的错误信息 状态码两部分。 自定义异常和异常处理所有自定义异常继承 RuntimeException, 在业务层抛出, 在统一的 ExceptionHandler 中进行处理。 错误返回数据格式错误信息格式: { "message": "<Prompt Title; required>", "errors": [ { "code": "<error code; required>", "description": "<Prompt Message; required>", "resource": "<optional>", "field":"<optional>", "document": "<optional>" } ] } 其中: 错误标题 message, 必填 错误代码 errors.code, 必填 错误展示信息 errors.description, 必填 资源 errors.resource, 可选 属性 errors.field, 可选 文档地址 errors.document, 可选 示例: 调用频率超过限制的 Response: Headers: Content-Type: application/json;charset=UTF-8 X-RateLimit-Limit: 3000 X-RateLimit-Reset: 1403162176516 X-RateLimit-Remaining: 0 { "message": "Something bad happened :-(", "errors": [ { "code": "rate_limit_exceeded", "description": "Too Many Requests. API rate limit exceeded", "document": "https://developer.github.com/v3/gists/" } ] } 应用状态码 Code HTTP Operation Body Contents Description 200 GET,PUT 资源 操作成功 201 POST 资源,元数据 对象创建成功 202 POST,PUT,DELETE,PATCH N/A 请求已经被接受 204 DELETE,PUT,PATCH N/A 操作已经执行成功,但是没有返回数据 301 GET link 资源已被移除 303 GET link 重定向 304 GET N/A 资源没有被修改 400 GET,PSOT,PUT,DELETE,PATCH 错误提示(消息) 参数列表错误(缺少,格式不匹配) 401 GET,PSOT,PUT,DELETE,PATCH 错误提示(消息) 未授权 403 GET,PSOT,PUT,DELETE,PATCH 错误提示(消息) 访问受限,授权过期 404 GET,PSOT,PUT,DELETE,PATCH 错误提示(消息) 资源,服务未找到 405 GET,PSOT,PUT,DELETE,PATCH 错误提示(消息) 不允许的http方法 409 GET,PSOT,PUT,DELETE,PATCH 错误提示(消息) 资源冲突,或者资源被锁定 415 GET,PSOT,PUT,DELETE,PATCH 错误提示(消息) 不支持的数据(媒体)类型 429 GET,PSOT,PUT,DELETE,PATCH 错误提示(消息) 请求过多被限制 500 GET,PSOT,PUT,DELETE,PATCH 错误提示(消息) 系统内部错误 501 GET,PSOT,PUT,DELETE,PATCH 错误提示(消息) 接口未实现 另一种常见形式是使用 Envelope:{ "status": { }, "data": { } } 出错时可仅有 status 而无 data 字段。 本规范采用无 Envelope 的格式。 安全RESTful API是无状态的也就是说用户请求的鉴权和 cookie 以及 session 无关,每一次请求都应该包含鉴权证明。 RESTful 服务使用 Oauth2 的方式进行调用授权,使用 http 请求头Authorization设置授权码; 必须使用User-Agent设置客户端信息, 无User-Agent请求头的请求应该被拒绝访问。 Request Header User-Agent: Data-Server-Client Authorzation: Bearer 383w9JKJLJFw4ewpie2wefmjdlJLDJF 说明: 授权码要放在 Http Header 中,可避免重定向传递时可能发生的授权码泄漏; 采用 JWT token。 缓存聚合 API把当前用户需要在第一时间内容加载的多个接口聚合成一个请求发送到服务端,服务端根据请求内容,一次性把所有数据合并返回。相比于页面级 API,具备更高的灵活性,同时又能很容易的实现页面级 API 的功能。 地址:/$version/aggApi 传入参数: data:[ {url:'api1',type:'get',data:{...}}, {url:'api2',type:'get',data:{...}}, {url:'api3',type:'get',data:{...}}, {url:'api4',type:'get',data:{...}} ] 返回数据: { status:0, msg:'', data:[ {status:0,msg:'',data:[]}, {status:-1,msg:'',data:{}}, {status:1,msg:'',data:{}}, {status:0,msg:'',data:[]}, ] } API 文档 阿里妈妈RAP Swagger 细节 使用ISO-8601格式表达时间字段,例如: 2014-04-05T14:30Z 响应:使用 HTTP Header 时,优先使用合适的标准头属性。用 X- 作为前缀自定义一个头属性,例如: X-Result-Fields。 其他 HATEOAS 参考 Best Practices for Designing a Pragmatic RESTful API | Vinay Sahni RESTful最佳实践 | Arccode’s blog 前后端完全分离之API设计 | Arccode’s blog HTTP Response Status Codes - YDN","tags":[{"name":"interface design","slug":"interface-design","permalink":"http://foudingzheng.info/tags/interface-design/"},{"name":"restful","slug":"restful","permalink":"http://foudingzheng.info/tags/restful/"},{"name":"code standard","slug":"code-standard","permalink":"http://foudingzheng.info/tags/code-standard/"}]},{"title":"研发与技术管理小结","date":"2016-03-28T02:20:05.000Z","path":"2016/03/pm-summary/","text":"在创业公司负责了几年的技术研发和管理,有自己的一点小心得,会陆续放在博客上来。 前言在 Infosys 学到的要点: Methodologies, Process, Code Standard/Guideline/Best Practices, “Checklist”。 创业公司研发部较少有纯管理性的职位,一般要身兼核心模块框架开发和团队管理的职责。这就要求负责人有较深的技术背景,上马能写核心模块,下马能指导成员遇到的遇到难题,建立起技术上的威信;管理上刚柔并济,让人心服。 创业公司,最大的不变就是变化。公司到处都不完善,而不完善的地方即是机会。做好这个心理准备,将成长得更快。 先说说贯穿于整个工作中的“团队建设”、“开会”、“任务排期”、“文档协作”和“绩效考核”。 团队建设公司,尤其是创业公司,最重要的是人,包括市场部人员、研发部人员和后勤人员等。要保持团队,尤其是核心成员的稳定性,不仅在待遇上处于业界中上水平,还须在团队氛围和技能提升上让成员觉得这是一个好的队伍,值得一起共事。 定位:服务于研发团队,给一线团队创造一个良好的工作环境。在内,创造令人愉快的环境和氛围;公司内,协调好与市场、后勤等其他部门的关系,包括为团队向公司争取应得的利益。 团队氛围:开放、互助 技术提升:构建内部的技术 wiki 并建立技术分享机制 定期的信息共享:让大家知道公司市场、运营情况;知道彼此在做什么,遇到了什么问题。 管理能力提升,同时建立起人才梯队 团队活动 饭后大家一起散步聊天(是真的散步-_-),是很好的方式 公司里没健身场地和器材,就在公司附近的健身场订了长期的羽毛球场地,每周三下班后一起去打球 一月一次的聚餐 提议中的家属日(尚未执行) 开会每次开会前需要会议 Owner 准备议题,并将相关文档发送给参会者熟悉;开会一定要形成决议,会后要有可执行的 Action 和任务分配到相关人员。 任务排期与跟踪排期:预估开发周期,制定项目开发进度,分解任务,分配任务。执行任务过程中,每天/两天应有进度跟踪。如果使用项目管理软件(如 Worktile 或 Kanboard),成员应及时更新状态。 方法:WBS 工具:看板模式,如 Worktile; 开发周期的预估很大程序上取决于负责人以前的经验。 分配任务时需分配到具体负责人,要有优先级、检查项、deadline。 了解每人性格、技术特点,从而匹配相关任务,并使其逐步提升。 文档协作创业公司不会按传统的研发流程按步就班地进行,更多强调的是快速开发。不仅非研发人员(老板,市场部等)不关注研发过程中的文档,连研发人员也很多对文档写作有排斥心理,认为写好代码,程序能跑就好了。的确,传统方式中的文档太多了,而且写了以后也不一定有人去看。但敏捷开发强调的是快速试错、快速迭代,而非简单粗暴,对比传统开发模型虽然并不强调文档,但并不代表不需要。对于一个项目,从开始就需要需求文档、产品原型文档、项目进度文档等等,而到了研发这一步,在系统实现、写代码之前最好的就是先”想”再做,而”想”的一种比较好的输出形式就是文档。对于一个软件系统,一般来说需要写的文档有以下几种: 系统业务流程文档:描述系统业务逻辑的文档,能清晰的说明整个业务的流程。 系统架构设计文档:对整个系统的架构的描述,需要包含系统的各个关键组成模块以及相关的各个关键技术点等。 系统功能模块概要设计/详细设计文档:对于某一个模块的流程、逻辑的描述。 数据DDL/DML文档: 与系统相关的数据库的DDL和DML文档,对于前者,是需要包含所有的操作的,而对于后者,必不可少的是查询语句,用来提供给DBA,来做查询sql的review,以保证索引的正确建立和查询语句的合理等。 系统部署文档:描述系统关键部分部署在哪里,需要做哪些配置。 系统发布ChangeLog:对系统每次发布的改动进行描述,包括数据库、缓存、数据队列、新增/变动了哪些依赖服务等。此外,对数据库、缓存这种关键服务的量化分析也可以写在这里。 文档选择的基本原则是:既不流于形式,又能保证研发过程的一致性和可维护性,减少不必更多的返工。 使用的工具可以是 WORD, Markdown 或在线协作工具。我推崇的格式是 markdown,比 WORD 文档更适合团队协作和版本追踪。Markdown 工具很多,可用 haroopad, pandao editor 等 文档的范例可参见这里。 绩效考核 绩效指标的确定:分职位,分阶段确定不同的指标。 Phases 研发各阶段梳理各阶段的角色、任务和工具链。 Req 需求调研需求梳理、分析是产品研发的前期阶段,易被忽略但极为重要的阶段。一个不符合市场需求的产品推倒重来,浪费的是研发人员的时间和公司在市场上的时机和表现。 需求调研:产品经理做需求调研,确定产品需求,编写需求文档(一张产品功能脑图或者一份功能列表),完成产品原型(手绘草图即可)需求的来源可能来自客户、老板和市场部。创业公司不一定有专职的产品经理,可能由技术负责人/研发人员同时兼任了。 原型设计:产品人员根据原型设计出一系列 UI 界面,包括产品业务流程图、交互原型。 需求评审:产品经理召集产品、UI、开发、测试等人员召开需求评审会议,确定最终产品的界面和交互。 工具:纸笔,墨刀 Design 设计及文档产出 概要设计和详细设计。很多时候详细设计都省略了,但基本的概要设计还是要的。文档中包括: 架构文档:建立系统总体结构,划分功能模块 流程交互图UML工具:StarUML, ProcessOn API 接口文档。 阿里妈妈RAP ,Swagger Dev 开发开发人员根据 UI 界面、需求文档和技术文档开始编写代码,开发模块上的功能。 Code Standard / Checklist 技术规范统一的技术规范和 checklist 利于团队协作,减少错误发生的可能性,增强可维护性。 数据库规范 代码规范 接口规范 可用性检查列表 测试规范 安全规范 QA 质量保证 Jenkins 静态分析:Findbugs;CheckStyle;单元测试覆盖率 > 95% Code Review 代码评审:每周一次的集体 Review 测试 Tools 使用的工具/资源 项目管理:phabricator: 可 code review,bug tracking,任务管理和知识整理等。 svn/git, Intellij Idea/Eclipse 前端资源 SPA 框架:Vue 2.0 Materializecss GitHub - JingwenTian/awesome-frontend puikinsh/gentelella: Free Bootstrap 3 Admin Template almasaeed2010/AdminLTE: AdminLTE - Free Premium Admin control Panel Theme ubold 后端工具:Spring Boot CLI, JHipster - Generate your Spring Boot + AngularJS apps! DB Schema: SchemaSpy 细节 缓存 事务控制 安全 注重监控模块 Deploy 自动化部署 开发环境 -> 测试环境 -> mock 环境 -> 线上环境 CI 工具:Jenkins Testing 测试 功能测试 性能测试: ab, JMeter 安全性测试 集成测试 移动端自动化测试工具 Bug 管理 工具:redmine. Redmine 官方安装较为繁琐,可使用 bitnami redmine 安装包 简易安装。 Go Live 上线Release Checklist Document,包括数据库更改、最新运行包,部署时间与方式,注意点,验证检查项等等。 DevOps db backup docker。团队里没有这方面的技术储备,不敢贸然上 docker。待研究。 其他效率/辅助工具 思维导图工具:MindManager, 百度脑图,XMind 即时沟通:BearyChat 移动办公app:今目标(也是厦门企业),阿里钉钉 其他 Total CommanderAlternatives under linux: Krusader, Gnome Commander gvim HeidiSQL: 数据库管理工具,完全可替代商业的 Navicat cmder babun: Win 上的微型 Linux 环境 Ngrok国内免费服务器——糖果科技:内网穿透利器 Launchy MobaXterm Atom NodeJS 小结对未建立或未完善完整技术团队和体系的公司来说: 组建核心技术团队。招聘到合适的人是件费时费心的事儿,团队磨合也是一个长期的过程。 研发基础设施:统一技术规范、工具链 搭建开发/测试/预发布/生产各环境:svn/git repo; maven/gradle repo; redmine;Jenkins 对于创业公司来说,需要建立一些必要的流程和制度。但流程这个东西必然需要经过试行,然后持续改进。 因此在初期就幻想建立一套完善的流程和制度是不切实际的。 资源和人力匮乏的情况下,一切以业务和产品优先。在过程中改进,只建立必要的流程和制度就行了。 所以,这些只是比较理想的流程,创业公司会因为时间、成本的原因,省去了很多流程,另外还有很多例外的情况,如开发倒逼需求,需求倒逼设计,不一而足,这要依实际情况调整了。这也是管理与开发的一大不同:对于机器,确定性、可预测性,而对于人性,则有更大的灵活性。但看着一个初步的 idea 在自己团队和整个公司的努力下,变成一个可触及的产品,还是很有成就感的。 参考 目标驱动的技术管理 - 软件工程师的自我修养 - 知乎专栏 研发体系这点事 - 后端技术杂谈 | 飒然Hang 我有一个 App 创意丨如何将其实现? - FenzoTech - 知乎专栏","tags":[{"name":"code standard","slug":"code-standard","permalink":"http://foudingzheng.info/tags/code-standard/"},{"name":"mgmt","slug":"mgmt","permalink":"http://foudingzheng.info/tags/mgmt/"},{"name":"dev phases","slug":"dev-phases","permalink":"http://foudingzheng.info/tags/dev-phases/"},{"name":"toolset","slug":"toolset","permalink":"http://foudingzheng.info/tags/toolset/"},{"name":"toolchain","slug":"toolchain","permalink":"http://foudingzheng.info/tags/toolchain/"}]},{"title":"Docker: Getting Feet Wet: Build the LAMP Stack","date":"2015-08-28T03:22:02.000Z","path":"2015/08/docker-getting-feet-wet/","text":"Setup Docker with VagrantGet the vagrant-based docker env from DockerRoot Vagrant Box, which is tiny (only 12 MB) and contains the lastest version of docker (Linux kernel v4.1.6 and GLIBC, Docker v1.8.1). The direct download address is https://atlas.hashicorp.com/ailispaw/boxes/docker-root/versions/1.0.0/providers/virtualbox.box). P.S. Some mini linux disto: Tiny Core Linux(on which boot2docker is based), yoctoproject, BusyBox, Buildroot. Data-Only Container (Container-as-Volumn Pattern)Tiny Docker Pieces, Loosely Joined: Since I’ve spelled it out in such exhaustive detail, this may all seem like much ado about nothing…but, now that I finally grok the idea, I can see how data-only containers are so useful. It’s the simplest way to share data among multiple containers. It makes it easy to upgrade the application (or process) that operates on the data. For example, say we wanted to generate PDF files in addition to HTML files. All we need to do to accomplish that is to create an updated pandoc-convert Docker image. Adding additional processes that operate on the data is equally easy. What if you wanted to push the HTML files to an Amazon S3, in addition to serving them locally? Just create a new s3sync Docker image. Build the LAMP Stack1. mysql data containerBuild mysql-data image and run (as container): docker run --name cont-db-data mariadb echo "Data-only container for mariadb" Note: not need to expose volume (-v /var/lib/mysql), see http://container-solutions.com/understanding-volumes-docker/ for explaination. To maint: SHX="bash --rcfile <(echo 'PS1=\\"\\n\\e[1;34m[\\t \\u@\\h \\W]\\e[m\\n\\$ \\"'; echo 'alias ll=\\"ls -ltra --color=auto\\"')" docker exec -it cont-mariadb bash -c "$SHX" Note: the $SHX here initializes the bash shell with --rcfile option (set the PS1 shell variable and ll alias). Or build with Dockerfile: cat >> $APPROOOT/dockerfiles/dbdata/Dockerfile <<EOF FROM stackbrew/busybox:latest MAINTAINER fouding zheng <fouding.zheng@gmail.com> VOLUME /var/lib/mysql CMD ["true"] EOF # KEY: volume is /var/lib/mysql, NOT arbitrary path! sudo docker build -t fouding/docker-mysql-data $APPROOOT/dockerfiles/dbdata sudo docker run --name cont-db-data fouding/docker-mysql-data true 2. cont-app-data containerHTMLROOT=/usr/share/nginx/html docker run --name cont-app-data -v $HTMLROOT stackbrew/busybox:latest echo "app data-only container" To maint the cont-app-data: docker run --rm --volumes-from cont-app-data -v /vagrant-dockerroot/:/app -it fouding/richarvey-nginx-php:addXdebug bash -c "$SHX" docker run --rm --volumes-from cont-app-data -v /vagrant-dockerroot/:/app -it stackbrew/busybox:latest /bin/sh Note: not docker exec -it cont-app-data bash -c "$SHX", as the cont is not in running state.Then: cp -r /app/iWshop /usr/share/nginx/html/ 3. db: mariadb(mysql) containerdocker run --name cont-mariadb --volumes-from cont-db-data -e MYSQL_USER=iwshop -e MYSQL_PASSWORD=1wsh0p -e MYSQL_DATABASE=iwshop -e MYSQL_ROOT_PASSWORD=my-secret-pw -d mariadb To maint: docker exec -it cont-mariadb bash -c "$SHX" OR docker run --rm --link cont-mariadb -it mariadb bash -c "$SHX" If “TERM environment variable not set” error: https://github.com/dockerfile/mariadb/issues/3 : docker exec -it mariadb bashmysqlproduces the following error message: TERM environment variable not set. Unsure why since echo $TERM returns dumb and issuing export TERM=dumb resolves issue. If the environment variable is explicitly issued in the dockerfile, the error message never appears, i.e. adding ENV TERM dumb 4. webnginx-php-fpm container: docker run --name cont-webserver -p 8000:80 --volumes-from cont-app-data --link cont-mariadb:mariadb -d fouding/richarvey-nginx-php To maint: docker run -it --rm fouding/richarvey-nginx-php bash -c "$SHX" docker run -it --rm fouding/richarvey-nginx-php bash -c "bash --rcfile <(echo 'PS1=\\"\\n\\e[1;34m[\\t \\u@ws \\W]\\e[m\\n\\$ \\"'; echo 'alias ll=\\"ls -ltra --color=auto\\"')" 5. testCreate a simple php file: cat >> ~/app/php/index.php << EOF <?php echo test php; phpinfo(); ?> EOF Open in browser: http://localhost:8000, or use cmdline: curl -i http://localhost:8000. 6. accessdocker exec -it cont-webserver bash Misc consider using the small phusion/baseimage image instead of the huge “ubuntu:14:04” image. Docker Usage Summary2 ways to communicate b/w 2 containers: Via volumes: cont-mariadb --volumes-from cont-db-data then the server can access the FILES in the dataCont => use as DATA STORAGE. Via container linking: cont-webserver: --link cont-mariadb:mariadb then the server can ping/connect to the dataCont => use as mysql server.KEY: dataCont: -v guestVolume to expose the vol, and server side: --volume-from dataCont [RI: not need to specify the volume here, which has been predefined by the docker run -v /guestVolume dataCont. The vol is: dataCont:guestVolume ] 2 types: docker run -d imgName docker run -ti imgName bash 2 ways to start a container: docker run -i -t imgName cmd docker run -d imgName [no cmd appended]","tags":[{"name":"docker","slug":"docker","permalink":"http://foudingzheng.info/tags/docker/"},{"name":"best practice","slug":"best-practice","permalink":"http://foudingzheng.info/tags/best-practice/"},{"name":"lamp","slug":"lamp","permalink":"http://foudingzheng.info/tags/lamp/"}]},{"title":"搭建 Vagrant 开发环境","date":"2015-08-20T08:23:01.000Z","path":"2015/08/build-dev-env-with-vagrant/","text":"BasicUsage One of the most useful senario with Vagrant is to setup a network of serveral computing nodes to mimic a distributed system. Or even better, use Docker. PreparationDownload offline file for instable network, esp. in case u r behind the “greatwall”:Search the box in https://atlas.hashicorp.com/ for box name, version, then follow the format https://atlas.hashicorp.com/puppetlabs/boxes/<boxName>/versions/<version>/providers/virtualbox.box to construct the downloading url. e.g. https://atlas.hashicorp.com/puppetlabs/boxes/centos-7.0-64-puppet/versions/1.0.2/providers/virtualbox.box Change the Storage Location Press Win+R and type systempropertiesadvanced to bring up the “System Properties” dialog, click “Environment Variables…” button, and in the “User vairalbes for $USER” section, add a variable named VAGRANT_HOME, whose value is D:\\your\\vagrant\\working\\path. Or use the command line: SETX VAGRANT_HOME "D:\\your\\vagrant\\working\\path" Change VirtualBox storage location: Open Virtalbox and navigate to File > Preference> General: Default Machine Folder: change to target location. Setup Vagrant Virtual MachineExecute below commands in cmder/cmd.exe: echo check vagrant_home: %vagrant_home% set BN=boot2docker-virtualbox set BOXPATH=D:\\tools\\vagrant-boxes\\boot2docker-virtualbox.box cd /d D:\\HashiCorp\\Vagrant\\myvagrantfile mkdir %BN% cd %BN% vagrant box add %BN% %BOXPATH% vagrant box list vagrant init %BN% And issue command vagrant up && vagrant ssh to startup and connect to the vagrant vm and install the dev tools manually, then key in vagrant package --output my-dev.box to export the whole box as a file which can then be shared b/w team members to setup the same dev env quickly.(If you are using cmder full version, the ssh command is right in your hands, so simply type vagrant ssh to connect to the vagrant box; otherwise, use a ssh client, say Putty, MobaXterm.) Or edit the Vagrantfile file to setup the dev env in script. TroubleshootingIf both host-only network and shared folder are enabled in Vagrantfile: config.vm.network "private_network", ip: "192.168.33.15" config.vm.synced_folder "e:", "/host_e" The host-only network is accessible, but the synced_folder cannot be found! (I don’t know whether this bug is in Vagrant, or reproducible only in my machine) Solution: create the /host_e directionary, then mount the drive in ~/.bashrc file manually: echo 'sudo mount -t vboxsf -o uid=$UID,gid=$(id -g) host_e /host_e' >> ~/.bashrc","tags":[{"name":"vagrant","slug":"vagrant","permalink":"http://foudingzheng.info/tags/vagrant/"},{"name":"dev","slug":"dev","permalink":"http://foudingzheng.info/tags/dev/"}]},{"title":"Pomodoro 番茄工作法","date":"2015-08-04T23:42:29.000Z","path":"2015/08/pomodoro/","text":"js app: Pomodoro by Zheng Fu: a simple yet sufficient pomodoro tool. begin of day: move activity from Activity Inventory Sheet to Todo Today sheet; end of day: review when finished a pomo: tag, cross finined task 4 pomo: longer rest (15-30min) if interrupted: mark “`” 原则: 尽早开始: 烂的开始是完成的一半 预留突发事情的时间 拆分:1z:各份真题,各份预测题 前有计划,后有总结 舍得:把做完的划掉;把用完的web/app关掉 一张图抓住时间管理的关键理念 清空脑袋 详细说明:一、5样工具 番茄计时器 铅笔和橡皮 今日待办清单(to do today),包括一部分留给计划外的待办事件 活动清单(Activity Inventory) 记录单 二、方法和步骤 把想要做的事情都记录在活动清单上,活动清单就像一个累积待办事项的仓库 每天早上从活动清单中挑选今天想做的事情抄入今日待办清单,按重要性排序并估测大致需要的番茄数 在今日待办清单上选出最重要的一件 开始一个番茄时间,倒计时25分钟,专注于你的工作,直到铃声响起 尽量保护你的番茄时间别被打断,如果有新想法或外部干扰,先记录下来,留到铃响之后处理 每个番茄时间结束后休息3-5分钟,4个番茄时间算一轮结束后休息15-30分钟 已经做完的事及时划掉 一天结束后在记录单中记录自己完成的番茄数,被打断的次数等等信息 将计划外且没完成的工作记回活动清单 分析和思考看看是否有需要改进 Ref: 番茄工作法学习笔记 什么是GTD?(GTD是关于什么的?) 一张图抓住时间管理的关键理念","tags":[{"name":"gtd","slug":"gtd","permalink":"http://foudingzheng.info/tags/gtd/"}]},{"title":"Welcome New Family Member","date":"2015-07-25T14:22:22.000Z","path":"2015/07/new-family-member/","text":"We are blessed with a cute girl on 24, Jun. Welcome, lele, the whole family are happy and enjoying with u! Love u, zz! U r the world to me. There is no place like ~.","tags":[{"name":"family","slug":"family","permalink":"http://foudingzheng.info/tags/family/"}]},{"title":"CentOS 上 Nginx 安装 modSecurity 模块","date":"2015-03-01T13:28:47.000Z","path":"2015/03/nginx-modsecurity-installation/","text":"Web应用防火墙 WAFWeb应用防护系统(Web Application Firewall, 简称:WAF)代表了一类新兴的信息安全技术,用以解决诸如防火墙一类传统设备束手无策的Web应用安全问题。与传统防火墙不同,WAF工作在应用层,因此对Web应用防护具有先天的技术优势。基于对Web应用业务和逻辑的深刻理解,WAF对来自Web应用程序客户端的各类请求进行内容检测和验证,确保其安全性与合法性,对非法的请求予以实时阻断,从而对各类网站站点进行有效防护。 ModSecurity是一个入侵侦测与防护引擎,其目的是为增强Web应用程序的安全性和保护Web应用程序避免遭受来自已知与未知的攻击。 Nginx 安装 modSecurity 模块 安装依赖 1234suyum install -y gcc make automake autoconf libtoolyum install -y pcre pcre-devel libxml2 libxml2-devel curl curl-devel httpd-devel 2. 编译 modSecurity 首先到 modSecurity 官网上下载最新版并编译: 12345678wget https://www.modsecurity.org/tarball/2.9.0/modsecurity-2.9.0.tar.gztar -xvpzf modsecurity-2.9.0.tar.gzcd modsecurity-2.9.0./autogen.sh./configure --enable-standalone-modulemake 会花1分钟左右的时间去编译。 3. 将 modSecurity 模块打包进 nginx 要从源码层级将 modSecurity 模块编译进 nginx。 123456789cd ~wget http://nginx.org/download/nginx-1.7.8.tar.gztar -xvpzf nginx-1.7.8.tar.gzcd nginx-1.7.8./configure --add-module=../modsecurity-2.9.0/nginx/modsecurity/makemake installcd /usr/local/nginx/sbin 安装后,nginx 位于 /usr/local/nginx/sbin/nginx。 4. 启用 modSecurity 12cd /usr/local/nginxvi conf/nginx.conf 1234location / { ModSecurityEnabled on; ModSecurityConfig modsecurity.conf;} 上面指定了modSecurity 的配置文件为 modsecurity.conf,下面来创建它: 1234# create am empty modsecurity.conftouch conf/modsecurity.conf# test the conf filesudo ./nginx -t 参考: 百度百科 How to install Mod_Security on Nginx ","tags":[{"name":"nginx","slug":"nginx","permalink":"http://foudingzheng.info/tags/nginx/"},{"name":"centos","slug":"centos","permalink":"http://foudingzheng.info/tags/centos/"},{"name":"waf","slug":"waf","permalink":"http://foudingzheng.info/tags/waf/"},{"name":"modSecurity","slug":"modSecurity","permalink":"http://foudingzheng.info/tags/modSecurity/"}]},{"title":"Linux Utilities","date":"2015-03-01T09:08:45.000Z","path":"2015/03/linux-utilities/","text":"networkcurlPOST json data:1curl -H "Content-Type: application/json" -d '{"username":"xyz","password":"xyz"}' http://foo.com/api/login Post a file:1curl -D - -F key1=value1 -F uploadedFile=@E:\\abc.txt http://foo.com/post For windows, single quotes around json did not work and I ended up escaping double quotes:1curl -X POST -H "Content-Type: application/json" -d "{ \\"key1\\": \\"value1\\" }" http://foo.com/api/method text browserselinks, lynx, etc.","tags":[{"name":"linux","slug":"linux","permalink":"http://foudingzheng.info/tags/linux/"}]},{"title":"Troubleshooting: POST Data Cannot Be Read From HttpServletRequest","date":"2015-02-13T07:24:46.000Z","path":"2015/02/Troubleshooting-POST-data-cannot-be-read-from-HttpServletRequest/","text":"问题遇到一个奇怪的问题:明明客户端有 POST 数据到服务端,但服务端就是“获取不到”数据。相关的代码如下: 123456@RequestMapping(value = \"goodsOrder\", method = RequestMethod.POST)public String goodsOrder(HttpServletRequest request, @RequestParam(value=\"page\", required=false, defaultValue=\"1\") int page) byte[] postBytes = IOUtils.toByteArray(request.getInputStream()); // ...} 此处,取到的 postBytes 总为空,而不是用户提交的数据。 原因及解决使用 Spring @RequestParam 来注入参数时,Spring 会去读取 HttpServletRequest request 的值,而request.getInputStream() 只能读取一次,所以就造成第二次去读参数时,获取不到数据了。 注意:ServletRequest.getParameter() 等方法也会隐式地读取请求数据,数据读取一次后得将其保存起来,因为不能再重新获取了。","tags":[{"name":"troubleshooting","slug":"troubleshooting","permalink":"http://foudingzheng.info/tags/troubleshooting/"},{"name":"SpringMVC","slug":"SpringMVC","permalink":"http://foudingzheng.info/tags/SpringMVC/"},{"name":"java","slug":"java","permalink":"http://foudingzheng.info/tags/java/"}]},{"title":"Nginx Configuration","date":"2015-02-12T06:25:05.000Z","path":"2015/02/nginx-conf/","text":"Part of configurations in nginx.conf1234567891011121314151617181920212223242526272829303132333435363738394041424344454647http { # Enable GZip compression gzip on; gzip_http_version 1.1; gzip_min_length 1000; gzip_buffers 16 8k; gzip_disable "MSIE [1-6] \\."; gzip_types text/html text/css text/xml application/x-javascript application/atom+xml text/plain gzip_vary on; # Set proxy cache path proxy_cache_path /var/nginx/cache keys_zone=one:10m; upstream tomcat_cluster { ip_hash; server ws1:12130; server ws2:12130; } server { listen 80; server_name localhost; location /portal/static/ { root D:/anyfish_web/nginx/; expires 30d; proxy_cache_valid 200 1d; proxy_cache_use_stale error timeout invalid_header updating http_500 http_502 http_504; } location /manager { allow 192.168.1.1/24; allow 127.0.0.1; deny all; } location / { proxy_pass http://tomcat_cluster; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host:$server_port; proxy_set_header X-Real-IP $remote_addr; } }} NotesGZip compression1234567gzip on;gzip_http_version 1.1;gzip_min_length 1000;gzip_buffers 16 8k;gzip_disable "MSIE [1-6] \\.";gzip_types text/html text/css text/xml application/x-javascript application/atom+xml text/plaingzip_vary on; Session persistenceSet ip_hash for session persistence. Serving static content123456location /portal/static/ { root D:/anyfish_web/nginx/; expires 30d; proxy_cache_valid 200 1d; proxy_cache_use_stale error timeout invalid_header updating http_500 http_502 http_504;} Change the root for URL starting with “/portal/static/“, letting it the serve the request with local static files. Server cluster/rack1234567891011upstream tomcat_cluster { ip_hash; server ws1:12130; server ws2:12130;}server { location / { proxy_pass http://tomcat_cluster; }} upstream is used for proxying requests to other servers. Conf for websocket:123proxy_http_version 1.1;proxy_set_header Upgrade $http_upgrade;proxy_set_header Connection "upgrade"; The first line tells Nginx to use HTTP/1.1 when communicating to the Node backend, which is required for WebSockets. The next two tell Nginx to respond to the Upgrade request which is initiated over HTTP by the browser when it wants to use a WebSocket. If the listening port is not 80:1proxy_set_header Host $host:$server_port; Note the $server_port part shoud be present. Without it, the redirection will jump to $host:80, rather than the specified port! Restrict access to the management pages by IP:12345location /manager { allow 192.168.1.1/24; allow 127.0.0.1; deny all;} Virtual HostEach server block represent the one virtual host with its own server_name. Each server blocks looks like:12345678server { listen 80; server_name blog.server1.com; location / { proxy_pass http://127.0.0.1:8282; }} here each server blocks bind (listen) the port 80, and this server block is only resonds when the server_name value (here blog.server1.com) is matched the HTTP header Host field location syntax1location [ = | ~ | ~* | ^~ ] uri { ... } Here, "~" - case sensitive matching "~*" - case insensitive matching "=" - forces a literal match between the request URI and the location parameter. ScriptingCheck the great presentation here Introduction to nginx.conf scripting.","tags":[{"name":"nginx","slug":"nginx","permalink":"http://foudingzheng.info/tags/nginx/"}]},{"title":"[转]常见的Web应用漏洞及其解决方法","date":"2015-02-11T03:23:27.000Z","path":"2015/02/webapp-attack-and-prevention/","text":"在 五个常见的Web应用漏洞及其解决方法 提到了几种常见的 Web 攻击方式及防范。 常见的Web应用漏洞及其解决方法开放Web应用安全项目(OWASP)很快会发布今年的10大Web应用安全漏洞清单。这个清单与去年并没有太大差别,这表明负责应用设计与开发的人仍然没能解决以前那些显而易见的错误。许多最常见的Web应用漏洞仍然广泛存在,许多恶意软件搜索和攻击这些漏洞,连黑客新手都能轻松做到。本文介绍了5个最常见的Web应用漏洞,以及企业该如何修复初级问题,对抗那些针对这些漏洞的攻击。 注入攻击和跨站脚本攻击Web应用主要有2种最常见的严重缺陷。首先是各种形式的注入攻击,其中包括SQL、操作系统、电子邮件和LDAP注入,它们的攻击方式都是在发给应用的命令或查询中夹带恶意数据。别有用心的数据可以让应用执行一些恶意命令或访问未授权数据。如果网站使用用户数据生成SQL查询,而不检查用户数据的合法性,那么攻击者就可能执行SQL注入。这样攻击者就可以直接向数据库提交恶意SQL查询和传输命令。举例来说,索尼的PlayStation数据库就曾经遭遇过SQL注入攻击,并植入未授权代码。 跨站脚本(XSS)攻击会将客户端脚本代码(如JavaScript)注入到Web应用的输出中,从而攻击应用的用户。只要访问受攻击的输出或页面,浏览器就会执行代码,让攻击者劫持用户会话,将用户重定向到一个恶意站点或者破坏网页显示效果。XSS攻击很可能出现在动态生成的页面内容中,通常应用会接受用户提供的数据而没有正确验证或转码。为了防御注入攻击和XSS攻击,应用程序应该配置为假定所有数据——无论是来自表单、URL、Cookie或应用的数据库,都是不可信来源。要检查所有处理用户提供数据的代码,保证它是有效的。验证函数需要清理所有可能有恶意作用的字符或字符串,然后再将它传给脚本和数据库。要检查输入数据的类型、长度、格式和范围。开发者应该使用现有的安全控制库,如OWASP的企业安全API或微软的反跨站脚本攻击库,而不要自行编写验证代码。此外,一定要检查所有从客户端接受的值,进行过滤和编码,然后再传回给用户。 身份验证和会话管理被攻破Web应用程序必须处理用户验证,并建立会话跟踪每一个用户请求,因为HTTP本身不具备这个功能。除非任何时候所有的身份验证信息和会话身份标识都进行加密,保证不受其他缺陷(如XSS)的攻击,否则攻击者就有可能劫持一个激活的会话,伪装成某个用户的身份。如果一个攻击者发现某个原始用户未注销的会话(路过攻击),那么所有帐号管理功能和事务都必须重新验证,即使用户有一个有效的会话ID。此外,在重要的事务中还应该考虑使用双因子身份验证。 为了发现身份验证和会话管理问题,企业要以执行代码检查和渗透测试。开发者可以使用自动化代码和漏洞扫描程序,发现潜在的安全问题。有一些地方通常需要特别注意,其中包括会话身份标识的处理方式和用户修改用户身份信息的方法。如果没有预算购买商业版本,那么也可以使用许多开源和简化版本软件,它们可以发现一些需要更仔细检查的代码或进程。 不安全的直接对象引用这是应用设计不当引起的另一个缺陷,它的根源是错误地假定用户总是会遵循应用程序的规则。例如,如果用户的帐号ID显示在页面的URL或隐藏域中,恶意用户可能会猜测其他用户的ID,然后再次提交请求访问他们的数据,特别是当ID值是可以猜测的时候。防止这种漏洞的最佳方法是使用随机、不可猜测的ID、文件名和对象名,而且不要暴露对象的真实名称。常见的错误暴露数据的位置是URL和超链接、隐藏表单域、ASP.NET的未保护视图状态、直接列表框、JavaScript代码和客户端对象(如Java Applet)。每次访问敏感文件或内容时,都要验证访问数据的用户已获得授权。 安全性配置不当支持Web应用程序的基础架构包含各种各样的设备和软件——服务器、防火墙、数据库、操作系统和应用软件。所有这些元素都必须正确配置和保证安全,应用程序只是运行在最低权限配置上,但是许多系统本身还不够安全。系统管理不当的一个主要原因是Web应用程序管理人员和基础架构支持人员从未接受过必要的培训。 为执行日常网络应用管理的人员提供足够的培训和资源,这是在开发过程中所有阶段保证安全性和保密性的重要条件。最后,要为Web应用程序安排一个渗透测试,处理所有敏感数据。这是一种主动评估应用抵抗攻击能力的方法,可以在受到攻击前发现系统漏洞。 下篇: nginx 安装 modSecurity","tags":[{"name":"nginx","slug":"nginx","permalink":"http://foudingzheng.info/tags/nginx/"},{"name":"waf","slug":"waf","permalink":"http://foudingzheng.info/tags/waf/"},{"name":"modSecurity","slug":"modSecurity","permalink":"http://foudingzheng.info/tags/modSecurity/"}]},{"title":"Team Management","date":"2015-02-06T14:05:08.000Z","path":"2015/02/team-management/","text":"情绪与效率这在 技术团队的情绪与效率 中已经讲得很好了: 在 GTD(Get Things Done)中对此有阐述『压力不是来自于任务本身,而是任务在大脑中的堵塞,带来的焦虑和心理的抵触』。当一件任务还没有完成时,持续到来的新任务会带来很大的心理压力,意志不够强大时,很容易导致执行力崩溃,进入一种任务怎么做都做不完的绝望状态。 一个健康的团队需要维持开发的节奏,具体操作可以是 每1-2周为一个周期,进行大的项目规划,研发任务占用时间最好不高于80%,之后每个人能有休息/自我充电的时间,在下个周期开始时,团队又能进入满体力值的状态。情绪的影响因素很多,简单列举几个很常见的: 研发节奏过于紧凑:在上一节中提到当开发的情绪体力持续透支时,会有恶劣的情绪问题。 这个在开发团队中并不少见。当开发节奏太过紧凑,团队不注意休整时,团队很容易负面情绪弥漫,而情绪一旦形成印象,便不会那么好消散。 薪酬倒挂:这个也是大家诟病 HR/Leader的重要原因,当一个团队薪酬内部增长太乏力时,内部人员会有流出,团队需要再招聘新人,而市场上平均待遇已经和之前不同,所以新招来的人员待遇往往也会水涨船高。 这个是很致命而且不好消解的。HR 太过节约成本,往往会对团队有致命的伤害。 与 Leader 理念/习惯 不合。 工作内容安排不当,太困难或太简单,或者与职业发展规划不符。 纯粹发泄。 …… 去年的主要问题在于公司里的加班情况过于严重,导致中后期的效率不高,总体工作完成量跟正常上班时间做的差不多,甚或更少。幸好管理层也有意识到这个问题,着手制定规范一些的流程。规范与近期效果是相对矛盾的,但为了后期的维护及长期的团队管理,取一个平衡点。开发流程可依实际情况引入敏捷开发模式。 坚持做的与不足之处坚持做下来的: 组里即时通讯群中的技术、生活分享 与各个组员私交不错,所以工作好开展。 GTD: 现使用 ToDoList (官网需要翻墙才能访问 -_-! 不过国内各大下载站都可以下载得到)做个人时间管理;团队里用 redmine 做 Bug Tracking, Task assignment, Knowledge Management 等。 之前在 CentOS 中装 redmine,依赖的包比较多,安装过程并不顺序,现在用的是 bitnami 的版本。 几点做得不好的: 之前在上海、苏州公司时,一两周有一次会有分享会议,技术的、生活的,甚至音乐、哲学方面的。现在几乎没有。虽说任务较重,但时间总是挤出来的。","tags":[{"name":"mgmt","slug":"mgmt","permalink":"http://foudingzheng.info/tags/mgmt/"}]},{"title":"Which FOSS To Use","date":"2015-02-04T13:32:45.000Z","path":"2015/02/use-which-foss/","text":"关于开源软件开源之殇 中提到 使用开源有风险前提:这里的开源产品,特指业务关联度高的中间件产品(比如分布式文件系统,消息队列中间件,即时通讯服务器,任务调度中间件,检索系统等)。最大的风险来自两方面: 使用过程中发现后续需求不满足 线上遇到BUG或者问题 使用过程中发现后续需求不满足 选择开源要谨慎基本使用开源中间件的公司在产品做到一定规模一定会遇到上面提到的风险和问题,大多数公司的选择也是一样,推倒重来,重新开发适合自己的中间件。比如淘宝,比如新浪,比如TWITER。相同的功能大家做了又做,不是炫耀自己有多牛,而是逼不得己。但并不是所有的开源都是洪水猛兽似的,通用性工具类的,特别是经过了大公司长时间验证过的通用性工具,是可以选用的,比如MYSQL, ZOOKEEPER, NGINX, HAPROXY, OPENFIRE, SOLR, KEEPALIVED, LIBEVENT, MINA, NETTY, TOMCAT, APACHE, LUCENE, REDIS, MONGODB, MEMCACHED 等等等等,是可以放心去选用的,前提是要对这些产品的配置和优化有充分的理解。 使用的开源软件 NGiNX + Tomcat 作为 web server Spring, SpringMVC Zookeeper 来做注册服务中心的管理者 Netty 与 Mina 师出同源,但前者的网络性能更佳、API接口更规范 简单的队列系统使用 Redis","tags":[{"name":"architecture","slug":"architecture","permalink":"http://foudingzheng.info/tags/architecture/"},{"name":"foss","slug":"foss","permalink":"http://foudingzheng.info/tags/foss/"}]},{"title":"Vim Tips","date":"2015-01-15T08:24:35.000Z","path":"2015/01/vim-tips/","text":"平时在 Windows 上使用 gVim 来编辑文本。将一些常用的技巧记录下来。 VIM tips常用指令/快捷键1234567891011121314# 光标移动gg, G, H, M, L, 8j, fcCtrl+F, Ctrl+B, Ctrl+D, Ctrl+UCtrl+O# # tab 标签栏的移动: 跳到下一个tab, 上一个tab, 第三个tab:gt, gT, 3gt# 查看打开的所有 buffer::ls# 跳到前一个,后一个,第3个buffer::bp, :bn, :b3 删除文件中的 ^M 字符1:%s/^M$//g 注:^M的输入:兼容模式下,要用 Ctrl+Q Ctrl+M / 标准模式下,则为 Ctrl+V Ctrl+M 删除偶数行1:g/^/+1 d 删除纯数字行当从网页上拷页代码时,若其代码插件不完善,将行号也拷贝下来了,一个行号一行。此时,可进入命令模式,用 Shift+v 及 <num>j, ctrl+d 选中该代码段,再按:,则最下方的状态栏出现 :'<,'>,输入指令:g/^\\d*$/d,即执行: 1:'<,'>g/^\\d*$/d 则会将只有行号的行删除。","tags":[{"name":"vim","slug":"vim","permalink":"http://foudingzheng.info/tags/vim/"}]},{"title":"Troubleshooting: SpringMVC Hang on Startup","date":"2015-01-14T08:31:56.000Z","path":"2015/01/Troubleshooting-SpringMVC-hang-on-startup/","text":"IssueThe webapp hang at:1INFO: Initializing Spring root WebApplicationContext when startup. SolutionEnable logging for Spring in logback.xml to check the startup process:1<logger name=\"org.springframework\" level=\"DEBUG\" />","tags":[{"name":"troubleshooting","slug":"troubleshooting","permalink":"http://foudingzheng.info/tags/troubleshooting/"},{"name":"SpringMVC","slug":"SpringMVC","permalink":"http://foudingzheng.info/tags/SpringMVC/"},{"name":"logging","slug":"logging","permalink":"http://foudingzheng.info/tags/logging/"}]},{"title":"Java Code Snippets","date":"2014-11-07T06:19:37.000Z","path":"2014/11/java-code-snippets/","text":"Byte array to hex string123public static String toHexString(byte[] array) { return javax.xml.bind.DatatypeConverter.printHexBinary(array);} Code in comments12345/** * <PRE> * insert code as you would anywhere else * </PRE> */ An Example from a java.awt.Event method javadoc: 123456789/** * <PRE> * int onmask = SHIFT_DOWN_MASK | BUTTON1_DOWN_MASK; * int offmask = CTRL_DOWN_MASK; * if ((event.getModifiersEx() & (onmask | offmask)) == onmask) { * ... * } * </PRE> */ Get field descriptions (names)123456789101112131415161718192021222324public static <T, K> Map<K, String> getFieldDescriptions(Class<T> clazz) { Map<K, String> map = new HashMap<K,String>(); for (Field f : clazz.getDeclaredFields()) { int mod = f.getModifiers(); if (Modifier.isPublic(mod) && Modifier.isFinal(mod) && Modifier.isStatic(mod)) { try { @SuppressWarnings(\"unchecked\") K fld = (K) f.get(null); Integer st = null; if (fld instanceof Integer) { st = (Integer) fld; } else if (fld instanceof Short) { st = (int) (short) (Short) fld; } map.put(fld, String.format(\"0x%04X, %s\", st&0xFFFFFFFF, f.getName())); } catch (Exception ex) { /* ignore */ } } } return map;} Check if AJAX request123public static boolean isAjax(HttpServletRequest request) { return \"XMLHttpRequest\".equalsIgnoreCase(request.getHeader(\"X-Requested-With\"));} Check if POST request123public static boolean isPost(HttpServletRequest request) { return \"POST\".equals(request.getMethod());} Get web root123public static String getWebRootPath() { return HttpUtils.class.getProtectionDomain().getCodeSource().getLocation().getPath().split(\"WEB-INF/\")[0];} Get client ip, event if the server is behind a proxy server (e.g. nginx)1234567891011public static String getIpAddr(HttpServletRequest request) { String ip = request.getHeader(\"x-forwarded-for\"); if (ip == null || ip.length() == 0 || \"unknown\".equalsIgnoreCase(ip)){ ip = request.getHeader(\"Proxy-Client-IP\"); } else if (ip == null || ip.length() == 0 || \"unknown\".equalsIgnoreCase(ip)) { ip = request.getHeader(\"WL-Proxy-Client-IP\"); } else if (ip == null || ip.length() == 0 || \"unknown\".equalsIgnoreCase(ip)) { ip = request.getRemoteAddr(); } return ip; } Get all net interfaces’ IP1234567891011121314151617181920public static List<String> getAllLocalIPs() { List<String> ipLst = new ArrayList<>(); try { Enumeration<NetworkInterface> e = NetworkInterface.getNetworkInterfaces(); while (e.hasMoreElements()) { NetworkInterface n = (NetworkInterface) e.nextElement(); Enumeration<InetAddress> ee = n.getInetAddresses(); while (ee.hasMoreElements()) { InetAddress i = (InetAddress) ee.nextElement(); ipLst.add(i.getHostAddress()); } } } catch (Exception e) { e.printStackTrace(); if (ipLst.size() == 0) { ipLst.add(LOCALHOST); } } return ipLst;} Set bits of a long variable123456789public static long setBits(long num, long val, int offset, int len) { int shift = Long.SIZE-offset-len; if (len > 1 || val == 0) { long ones = ((1L << len) - 1) << shift; //[] 1L return ((num & ~ones) | (val << shift)); } else { return (long) (num | (1 << shift)); }}","tags":[{"name":"java","slug":"java","permalink":"http://foudingzheng.info/tags/java/"},{"name":"code snippet","slug":"code-snippet","permalink":"http://foudingzheng.info/tags/code-snippet/"}]},{"title":"Tomcat Tips","date":"2014-11-03T09:25:36.000Z","path":"2014/11/tomcat-tips/","text":"Some tips for Tomcat7. Move the constant lib as external libs: Step 1: Copy the lib to somewhere else, e.g. ${catalina.home}/anyfish-lib Step 2: Eclipse: Properties for app > Deployment Assembly > Remove ‘Maven Dependencies’ Step 3: Add following config in /conf/context.xml: 1<Loader className=\"org.apache.catalina.loader.VirtualWebappLoader\" virtualClasspath=\"${catalina.home}/anyfish-lib/*.jar\" /> So the final packaged .war file size is reduced dramatically! To achive Step 2 in maven, edit the pom.xml file: 1234567891011121314151617<properties> <maven.build.timestamp.format>yyyyMMddHHmm</maven.build.timestamp.format></properties><build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-war-plugin</artifactId> <version>2.4</version> <configuration> <warName>${project.artifactId}##${maven.build.timestamp}</warName> <packagingExcludes>%regex[WEB-INF/lib/(?!mylib-1.0.0-SNAPSHOT).*.jar]</packagingExcludes> </configuration> </plugin> </plugins></build> Note the <packagingExcludes>%regex[WEB-INF/lib/(?!mylib-1.0.0-SNAPSHOT).*.jar]</packagingExcludes> config above (Don’t package the common spring, logging libaries etc into .war file; keep only mylib.jar). Deploy new version w/o shutting down tomcatTo deploy a new version of webapp without shutting down tomcat, use parallel deployment in tomcat7, refer to tomcat parallel deploy. Note the <warName>${project.artifactId}##${maven.build.timestamp}</warName> config above.","tags":[{"name":"tomcat","slug":"tomcat","permalink":"http://foudingzheng.info/tags/tomcat/"}]},{"title":"Maven Tips","date":"2014-10-11T07:51:02.000Z","path":"2014/10/maven-tips/","text":"使用国内的镜像地址mavn 中央库默认的下载地址在国外,下载速度不稳定。开源中国做了国内镜像,参考其帮助文档配置其为中央库: 备份 Maven 的安装目录下的 conf/settings.xml 文件,再编辑它: 12345678910111213141516171819202122232425262728293031323334353637<mirrors> <!-- http://maven.oschina.net/static/help.html --> <mirror> <id>nexus-osc</id> <mirrorOf>*</mirrorOf> <name>Nexus OSC</name> <url>http://maven.oschina.net/content/groups/public/</url> </mirror></mirrors><profiles> <!-- http://maven.oschina.net/static/help.html --> <profile> <id>jdk-1.4</id> <activation><jdk>1.4</jdk></activation> <repositories> <repository> <id>nexus</id> <name> local private nexus</name> <url>http://maven.oschina.net/content/groups/public</url> <releases><enabled>true</enabled></releases> <snapshots><enabled>false</enabled></snapshots> </repository> </repositories> <pluginRepositories> <pluginRepository> <id>nexus</id> <name>local private enxus</name> <url>http://maven.oschina.net/content/groups/public</url> <releases><enabled>true</enabled></releases> <snapshots><enabled>false</enabled></snapshots> </pluginRepository> </pluginRepositories> </profile></profiles> 自定义生成 war 包的内容和文件名1234567891011121314151617<properties> <maven.build.timestamp.format>yyyyMMddHHmm</maven.build.timestamp.format></properties><build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-war-plugin</artifactId> <version>2.4</version> <configuration> <warName>${project.artifactId}##${maven.build.timestamp}</warName> <packagingExcludes>%regex[WEB-INF/lib/(?!mylib-1.0.0-SNAPSHOT).*.jar]</packagingExcludes> </configuration> </plugin> </plugins></build> 以上配置, 在生成的 war 包名字的后面加上打包时间; 不 将公共的 spring, json 的相关 jar 包打到最终 war 包中,只保留自己的、经常会变化的 mylib-1.0.0-SNAPSHOT,参见Tomcat Tips。","tags":[{"name":"maven","slug":"maven","permalink":"http://foudingzheng.info/tags/maven/"}]},{"title":"Tomcat 并行部署","date":"2014-09-22T04:53:05.000Z","path":"2014/09/parallel-deploy-script/","text":"背景知识: tomcat的并行部署部署新版本时,一般要先停掉旧的应用,复制新应用后,再启动tomcat,这就造成中间有一段时间 tomcat 是无法对外服务的。 从Tomcat 7.0.23+开始,可以在不关闭 Tomcat 的情况下同时部署相同context path的多个不同版本的web应用。war包的命名格式形如context.war, context##2.war,context##3.war。 当用户访问 http://[url]/context 时,规则如下: 如果当前请求没有session信息,则使用最新的war应用版本 如果当前请求有session,使用session对应的war应用版本 如果当前请求有session信息,却找不到相应的war应用版本,则使用最新的war应用版本(context##3.war) 实现并行部署 pom.xml 中配置生成的 war 包加时间戳: 12345678910111213141516<properties> <maven.build.timestamp.format>yyyyMMddHHmm</maven.build.timestamp.format></properties><build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-war-plugin</artifactId> <version>2.4</version> <configuration> <warName>${project.artifactId}##${maven.build.timestamp}</warName> <packagingExcludes>%regex[WEB-INF/lib/(?!mylib-1.0.0-SNAPSHOT).*.jar]</packagingExcludes> </configuration> </plugin> </plugins></build> 在项目根目录下执行命令 mvn package 生成 war 包,如 portal##20140922.war。 2. 部署 将 war 包上传到/anyfish_web/war/warHub 下后,执行部署脚本 parallelDeploy.sh (脚本内容见下) 部署脚本并行部署脚本 parallelDeploy.sh 的功能:搜寻指定目录下最新的 war 包,然后依 Tomcat 格式,在 $TOMCAT_HOME/conf/Catalina/localhost 创建配置文件(如 portal##20140922.xml),以使 Tomcat 感知并部署此 web 工程。 1234567891011121314151617# confctx=portalwarFolder=/anyfish_web/war/warHubdeployFolder=/anyfish_web/warconfFolder=/Tomcat/conf/Catalina/localhost# ---latestWarFile=$(basename `ls -tr $warFolder/*.war | tail -n 1`)echo --- latestWarFile: $latestWarFilecp $warFolder/$latestWarFile $deployFolder/confFile=$(echo $latestWarFile | sed 's/.*\\#\\#\\(.*\\)\\.war/\\1/')ctxXml=$confFolder/$ctx##$confFile.xml# generate context conf file for tomcat to deployecho --- ctxXml: $ctxXmlctxCont='<Context displayName=\"'$ctx'\" docBase=\"/anyfish_web/war/'$latestWarFile'\" reloadable=\"true\" />'echo $ctxCont > $ctxXml","tags":[{"name":"script","slug":"script","permalink":"http://foudingzheng.info/tags/script/"},{"name":"tomcat","slug":"tomcat","permalink":"http://foudingzheng.info/tags/tomcat/"},{"name":"deploy","slug":"deploy","permalink":"http://foudingzheng.info/tags/deploy/"}]},{"title":"Change Shell Prompt Color","date":"2014-09-19T11:53:05.000Z","path":"2014/09/change-shell-prompt-color/","text":"Normal user: vi ~/.bashrc 1export PS1=\"\\n\\e[0;33m[\\t \\u@\\h \\W]\\$ \\e[m\" Root user (set as red #): 1export PS1=\"\\n\\e[0;31m[\\t \\u@\\h \\W]\\$ \\e[m\" References: Change The Color of My Shell Prompt Under Linux or UNIX Bash Prompt Escape Sequences","tags":[{"name":"linux","slug":"linux","permalink":"http://foudingzheng.info/tags/linux/"},{"name":"shell","slug":"shell","permalink":"http://foudingzheng.info/tags/shell/"}]},{"title":"Recognizing Session Timeout","date":"2014-09-07T07:13:17.000Z","path":"2014/09/recognizing-session-timeout/","text":"You need to implement the HttpSessionListener interface. It receives notification events when session is created, or destroyed. In particular, its method sessionDestroyed(HttpSessionEvent se) gets called when the session is destroyed, which happens after timeout period has finished / session was invalidated. You can get the information stored in the session via HttpSessionEvent#getSession() call, and later do any arrangements that are necessary with the session. Also, be sure to register your session listener in web.xml: 123<listener> <listener-class>FQN of your sessin listener implementation</listener-class></listener> If you ultimately want to distinguish between invalidation and session timeout you could use the following line in your listener:12long now = new java.util.Date().getTime();boolean timeout = (now - session.getLastAccessedTime()) >= ((long)session.getMaxInactiveInterval() * 1000L); Reference: stackoverflow","tags":[{"name":"web","slug":"web","permalink":"http://foudingzheng.info/tags/web/"}]},{"title":"Use Encrypted Password Property in Spring Properties File","date":"2014-08-23T06:08:07.000Z","path":"2014/08/use-encrypted-password-property-in-spring-properties-file/","text":"Add dependencies in pom.xml:12345678910<dependency> <groupId>org.jasypt</groupId> <artifactId>jasypt</artifactId> <version>1.9.2</version></dependency><dependency> <groupId>org.jasypt</groupId> <artifactId>jasypt-spring31</artifactId> <version>1.9.2</version></dependency> Config applicationContextm.xml: 123456789101112131415161718<bean id=\"textEncryptor\" class=\"org.jasypt.util.text.BasicTextEncryptor\" p:password=\"ur-pa55w0rd\"/><beans profile=\"development\"> <!-- <context:property-placeholder ignore-resource-not-found=\"true\" location=\"classpath*:/application.properties, classpath*:/application.development.properties\" file-encoding=\"utf-8\" /> --> <bean class=\"org.jasypt.spring31.properties.EncryptablePropertySourcesPlaceholderConfigurer\"> <constructor-arg ref=\"textEncryptor\" /> <property name=\"locations\"> <list> <value>classpath*:/application.properties</value> <value>classpath*:/application.development.properties</value> </list> </property> </bean></beans> P.S. Below snippet is not working. Wonder why.123<bean class=\"org.jasypt.spring31.properties.EncryptablePropertyPlaceholderConfigurer\" p:locations=\"classpath*:/application.properties, classpath*:/application.development.properties\" c:textEncryptor-ref=\"textEncryptor\" />","tags":[{"name":"java","slug":"java","permalink":"http://foudingzheng.info/tags/java/"},{"name":"spring","slug":"spring","permalink":"http://foudingzheng.info/tags/spring/"},{"name":"encryption","slug":"encryption","permalink":"http://foudingzheng.info/tags/encryption/"}]},{"title":"Run a Program as Adminitrator via The Command Line","date":"2014-08-12T07:23:15.000Z","path":"2014/08/run-a-program-as-adminitrator-via-the-command-line/","text":"Create a *.vbs script file:1234Set objShell = CreateObject("Shell.Application")prog = "c:/path/to/the/executable.exe"args = ""objShell.ShellExecute prog, args, "", "runas" Then run it from command line, or via Launchy.","tags":[{"name":"win","slug":"win","permalink":"http://foudingzheng.info/tags/win/"},{"name":"cmd","slug":"cmd","permalink":"http://foudingzheng.info/tags/cmd/"}]},{"title":"Verifying Which Ports Are Listening","date":"2014-07-12T08:10:17.000Z","path":"2014/07/Verifying-Which-Ports-Are-Listening/","text":"12nmap -sT -O localhostnetstat -tuplen Troubleshooting: This output shows the system is running portmap due to the presence of the sunrpc service. However, there is also a mystery service on port 834. To check if the port is associated with the official list of known services, type: 1cat /etc/services | grep 834 ===> 1netstat -anp | grep 834 The command returns the following output: tcp 0 0 0.0.0.0:834 0.0.0.0:* LISTEN 653/ypbind ===> 1lsof -i | grep 834","tags":[{"name":"troubleshooting","slug":"troubleshooting","permalink":"http://foudingzheng.info/tags/troubleshooting/"},{"name":"linux","slug":"linux","permalink":"http://foudingzheng.info/tags/linux/"},{"name":"port","slug":"port","permalink":"http://foudingzheng.info/tags/port/"}]},{"title":"Import File to sqlite script","date":"2014-05-04T01:18:45.000Z","path":"2014/05/import-file-to-sqlite-script/","text":"写的一个 Python 的练手的小工具。已有一个单词的数据表,其中有单词、音标、意思、发音,发音的音频文件放在一个目录下,文件名为[word].mp3。现要将这些音频文件导入表中;使用 Tkinter 做 GUI 界面。123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192#!/usr/bin/python# -*- coding: utf-8 -*-import sqlite3 as liteimport sysimport pdbimport tkMessageBoximport Tkinter as tkimport ttkimport tkFileDialogfrom Tkinter import Tk, Text, Entry, OptionMenu, BOTH, W, N, E, S, StringVarfrom ttk import Frame, Button, Label, Styleprint 'sqlite version: %s' % lite.versiondef readFile(filePath): try: fin = open(filePath, \"rb\") return fin.read() except IOError, e: print \"Error %d: %s\" % (e.args[0], e.args[1]) sys.exit(1) finally: if fin: fin.close()class SqliteUtil: def __init__(self, sqliteFile): self.conn = lite.connect(sqliteFile) self.cur = self.conn.cursor() def fetchall(self, sqlStmt): self.cur.execute(sqlStmt) return self.cur.fetchall() def fetchone(self, sqlStmt): self.cur.execute(sqlStmt) return self.cur.fetchone() def setBinData(self, tableName, binColumnName, filePath, whereClause): try: data = readFile(filePath) binData = lite.Binary(data) sql = 'UPDATE %s SET %s=? WHERE %s' % (tableName, binColumnName, whereClause) print sql self.cur.execute(sql, (binData,) ) print 'Affected rows using sqlite.Binary(): %d' % self.cur.rowcount self.cur.execute(sql, [buffer(data)]) print 'Affected rows using buffer(): %d' % self.cur.rowcount self.conn.commit() except lite.Error, e: if self.conn: self.conn.rollback() print \"Error %s:\" % e.args[0] sys.exit(1) #finally: #if conn: #conn.close()class WordFrame(Frame): def __init__(self, parent): Frame.__init__(self, parent) self.parent = parent self.initUI() self.sqliteUtil = None @staticmethod def refreshOptionMenu(om, oldVarChoice, newChoices): # Reset var and delete all old options oldVarChoice.set('') #oldVarChoice.set(newChoices[0]) om['menu'].delete(0, 'end') # Insert list of new options (tk._setit hooks them up to var) for choice in newChoices: om['menu'].add_command(label=choice, command=tk._setit(oldVarChoice, choice)) def browseSqlite(self): ftypes = [('Sqlite files', '*.sqlite'), ('All files', '*')] dlg = tkFileDialog.Open(self, filetypes=ftypes) sqliteFile = dlg.show() if sqliteFile != '': self.varFilename.set(sqliteFile) self.sqliteUtil = SqliteUtil(sqliteFile) tables = self.sqliteUtil.fetchall(\"SELECT name FROM sqlite_master WHERE type='table'\") #self.varTable.set(tuple(map(lambda x: x[0], tables))) self.refreshOptionMenu(self.omTables, self.varTable, map(lambda x: x[0], tables)) # fetch the cols cols = self.sqliteUtil.fetchall('PRAGMA table_info(%s)' % (tables[0])) self.refreshOptionMenu(self.omCols, self.varCol, map(lambda x: x[1], cols)) # pdb.set_trace() def initUI(self): self.parent.title(\"导入音频数据\") self.style = ttk.Style() self.style.theme_use(\"default\") self.pack(fill=BOTH, expand=1) self.centerWindow() # self.columnconfigure(1, weight=1) # row 0: file selection panelFileSelection = Frame(self) Label(panelFileSelection, text=\"sqlite文件:\").pack(side=\"left\", fill=None, expand=False) self.varFilename=StringVar() self.txtFileName = Entry(panelFileSelection, textvariable=self.varFilename, width=60) self.txtFileName.pack(side=\"left\", fill=None, expand=True) btnBrowse = Button(panelFileSelection, text=\"...\", command=self.browseSqlite) #btnBrowser.grid(row=0, column=4, pady=4) btnBrowse.pack(side=\"right\", fill=None, expand=False) panelFileSelection.grid(row=0, column=0, columnspan=4, sticky=\"we\") # Row 1: word table/audio column name self.varTable = StringVar(self) Label(self, text=\"表名:\").grid(row=1, sticky=W, padx=5, pady=4) self.omTables = OptionMenu(self, self.varTable, '') self.omTables.grid(row=1, column=1, sticky=E+W) self.omTables['menu'].bind(\"<Unmap>\", self.onSelectTable) self.varCol = StringVar(self) Label(self, text=\"列名:\").grid(row=1, column=2, sticky=E, padx=5, pady=4) self.omCols = OptionMenu(self, self.varCol, '') self.omCols.grid(row=1, column=3, sticky=E+W) # Row 3: close/import buttons btnImport = Button(self, text=\"导入\", command=self.importAudios) btnImport.grid(row=2, column=2, padx=5, pady=5) btnClose = Button(self, text=\"关闭\", command=self.quit) btnClose.grid(row=2, column=1, padx=5, pady=5) def quit(self): print 'in quit()' print self.sqliteUtil if not (self.sqliteUtil is None) and not (self.sqliteUtil.conn is None): print 'to close conn' self.sqliteUtil.conn.close() print 'to exit()' sys.exit(0) def importAudios(self): # tkMessageBox.showinfo(\"col: \", self.varCol.get()) # use loop here audioFile = 'C:/Users/fouding/Desktop/fetion_received_files/a bas.mp3' self.sqliteUtil.setBinData(self.varTable.get(), self.varCol.get(), audioFile, \"word='think'\") def onSelectTable(self, val): print 'onSelectTable' sender = val.widget tblName = sender.get(sender.curselection()) # fetch the cols cols = self.sqliteUtil.fetchall('PRAGMA table_info(%s)' % (tblName)) self.refreshOptionMenu(self.omCols, self.varCol, map(lambda x: x[1], cols)) def centerWindow(self): w = 500 h = 200 sw = self.parent.winfo_screenwidth() sh = self.parent.winfo_screenheight() x = (sw - w) / 2 y = (sh - h) / 2 self.parent.geometry('%dx%d+%d+%d' % (w, h, x, y))def main(): root = tk.Tk() app = WordFrame(root) root.mainloop()if __name__ == '__main__': main()","tags":[{"name":"python","slug":"python","permalink":"http://foudingzheng.info/tags/python/"},{"name":"sqlite","slug":"sqlite","permalink":"http://foudingzheng.info/tags/sqlite/"},{"name":"script","slug":"script","permalink":"http://foudingzheng.info/tags/script/"}]},{"title":"Keep A Process Running After Terminal Disconnected","date":"2014-04-29T01:38:08.000Z","path":"2014/04/keep-a-process-running-after-terminal-disconnected/","text":"SenarinoTo leave a process running during the night, and continue the work next morning. Solutionnohup1nohup blah & Substitute your process name for blah. screenscreen is a powerful utility, which allows you to disconnect from the server while all of your processes continue to run. %% js: Detach from a running screen session leaving it running in the background: Control + A + D (not case sensitive) %% Re-attach to a specific screen you’ve named: screen -R somename Useful screen commands:List a particular users screen sessions: screen -list username/ (the forward slash is important) List your own active screen sessions: screen -ls Re-attach to a specific users screen and session: screen -x username/shared-session Start a screen session and give it a unique name: screen -S somename Detach from a running screen session leaving it running in the background: Hit the key combination: Control + A/a + D/d (not case sensitive) Re-attach to a specific screen you’ve named: screen -R somename Power detach a screen that you are logged into from another location:This is helpful if you’ve been accidentally disconnected from ssh while in a remote screen session and it’s still attached.screen -D somename","tags":[{"name":"linux","slug":"linux","permalink":"http://foudingzheng.info/tags/linux/"},{"name":"shell","slug":"shell","permalink":"http://foudingzheng.info/tags/shell/"},{"name":"utility","slug":"utility","permalink":"http://foudingzheng.info/tags/utility/"}]},{"title":"Hello World","date":"2014-02-18T02:10:00.000Z","path":"2014/02/hello-world/","text":"Welcome here! This is a blog about my life and career, powered by GitHub pages and hexo. I can be reached at fouding_zheng[at]outlook.com.","tags":[]}]