Categories
木有技术

Apache服务器配置WoSign(沃通)SSL证书

参考文献
使用沃通签了一份SSL证书,其实配置的过程挺简单的,看参考文献基本能搞定。需要注意的是,本教程只开启了HTTPS站点的访问,并不强制跳转HTTP到HTTPS。
我的服务器是Ubuntu+Apache,按照apt-get安装的apache2应该在这个/etc/apache2 目录下
可以看到目录大概是这个结构:

total 80
-rw-r--r-- 1 root root 7114 Dec 31 05:57 apache2.conf
drwxr-xr-x 2 root root 4096 Dec 31 05:31 conf-available
drwxr-xr-x 2 root root 4096 Dec 31 05:31 conf-enabled
-rw-r--r-- 1 root root 1782 May 20 2015 envvars
-rw-r--r-- 1 root root 31063 May 20 2015 magic
drwxr-xr-x 2 root root 12288 Apr 28 07:03 mods-available
drwxr-xr-x 2 root root 4096 May 24 11:17 mods-enabled
-rw-r--r-- 1 root root 320 May 20 2015 ports.conf
drwxr-xr-x 2 root root 4096 Dec 31 05:56 sites-available
drwxr-xr-x 2 root root 4096 May 24 11:27 sites-enabled

其中sites-available目录下应该有个默认的https配置的,我这边就有个default-ssl.conf ,然后参考本文最开始提供的参考文献链接,编辑这个文件,做这么几步:

  1. 配置公钥:修改字段SSLCertificateFile conf/ssl/test.wosign.com.crt ,SSLCertificateFile字段后面的文件地址是你存储的公钥文件(以我的站为例,wosign发给我的压缩包里有for Apache.zip,压缩包里面就有个 _boweihe.me.crt);
  2. 配置私钥:修改字段SSLCertificateKeyFile conf/ssl/test.wosign.com.key 同理,自行修改私钥文件地址(私钥文件后缀是.key)
  3. 配置服务器证书链:找到以#SSLCertificateChainFile 开头的一行,删除行开头的#号注释,并修改字段对应值为你的证书链文件(1_root_bundle.crt)地址,如SSLCertificateChainFile conf/ssl/1_root_bundle.crt

然后需要启用这个HTTPS站点(配置文件的其他内容修改可以参考你自己本身已经启用的http站的,可能要注意的是webroot要保持一致什么的),启用的命令很简单

a2ensite default-ssl

后面的default-ssl是你刚才修改的default-ssl.conf文件的名字,如果你起了另外的名字,那相应修改。命令执行成功后,会提示

To activate the new configuration, you need to run:
  service apache2 reload

照着做就行了,重新加载下apache服务:

service apache2 reload

至此你的站就可以用https协议访问了,如图
https
可能遇到的问题是,站点里部分资源是非HTTPS协议的,这时候图表就不能变绿了。比如我使用了七牛云的静态文件缓存,还得去人工提交一下申请,开通HTTPS静态资源访问的协议。

Categories
不学无术 木有技术

在SpringMVC 4 中用注释(Annotation)方式使用 Google Kaptcha (Captcha)

原创内容,转载请注明来自http://boweihe.me/?p=2026

近期在努力学习SpringMVC,因为之前对JSP也是一知半解的,干脆拿了本《Spring in Action》(4th edition)啃,发现使用注释的方式比用xml来的有意思一些。网站中要用到验证码,目前能找到的文档都是用xml配置的,感觉有点不爽,决定学我党“摸着石头过河”一次,希望不要naive了..

Maven库

Google自家的库估计是没了,大概是时间太久远了吧。我用的Maven库是这个

<dependency>
            <groupId>com.github.penggle</groupId>
            <artifactId>kaptcha</artifactId>
            <version>2.3.2</version>
</dependency>

Bean配置文件

需要把原有的applicationContext.xml用注释的方式实现,其实就是让Spring找到个可以用的Bean并加载相关配置。
 
我构造了一个类来搞定这个事情,里面的配置参数会从application.properties文件中读取,我仅仅实现了我需要的几个参数,反正如果要加的话就在kaptchaProperties方法里面写就可以,然后Autowire一个Environment用来读取文件配置参数。主要是@Configuration 注释,这个是告诉Spring我是个配置类,这个还是从Hibernate配置中学过来的,哈哈。
声明出来的这个@Bean(name = “captchaProducer”) 就是后面Controller里面要用的了

package edu.inlab.config;
import com.google.code.kaptcha.impl.DefaultKaptcha;
import com.google.code.kaptcha.util.Config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
import java.util.Properties;
/**
 * Created by inlab-dell on 2016/5/17.
 */
@Configuration
@PropertySource(value = {"classpath:application.properties"})
public class KaptchaConfig {
    @Autowired
    private Environment environment;
    private DefaultKaptcha kaptcha;
    @Bean(name = "captchaProducer")
    public DefaultKaptcha getKaptchaProducer(){
        if(null == kaptcha) {
            kaptcha = new DefaultKaptcha();
            kaptcha.setConfig(getKaptchaConfig());
        }
        return kaptcha;
    }
    @Bean
    public Config getKaptchaConfig(){
        return new Config(kaptchaProperties());
    }
    private Properties kaptchaProperties(){
        Properties properties = new Properties();
        properties.put("kaptcha.image.width",
                environment.getRequiredProperty("kaptcha.image.width"));
        properties.put("kaptcha.image.height",
                environment.getRequiredProperty("kaptcha.image.height"));
        properties.put("kaptcha.textproducer.char.string",
                environment.getRequiredProperty("kaptcha.textproducer.char.string"));
        properties.put("kaptcha.textproducer.char.length",
                environment.getRequiredProperty("kaptcha.textproducer.char.length"));
        return properties;
    }
}

对应的application.properties,具体含义可以看参考文献里,解释的很清楚了。

kaptcha.image.width = 200
kaptcha.image.height = 50
kaptcha.textproducer.char.string = ABCDEFGHKLMNPQRSTWXY3456789
kaptcha.textproducer.char.length = 6

CaptchaController 控制器

项目在实习前怕是赶不完来了,我还是少花点时间写博客吧。
控制器的实现Wiki上都有,主要的是要@Autowire之前我们做好的那个Bean,大概是这样。其实就是个基于set方法的注入嘛~

@Controller
@RequestMapping("/captcha")
public class CaptchaController {
    private Producer captchaProducer;
    @Autowired
    public void setCaptchaProducer(Producer captchaProducer) {
        this.captchaProducer = captchaProducer;
    }
    @RequestMapping(method = RequestMethod.GET)
    public ModelAndView handleRequest(HttpServletRequest request,
                  HttpServletResponse response) throws Exception{
        // ...
    }
}

 

参考文献:

  1. 在springmvc项目中使用kaptcha生成验证码
  2. 简单Maven的Web项目之验证码(Kaptcha篇)
  3. Spring mvc框架下使用kaptcha生成验证码
  4. https://code.google.com/archive/p/kaptcha/wikis

 

Categories
不学无术

LeetCode 119. Pascal's Triangle II

题目

Given an index k, return the kth row of the Pascal’s triangle.
For example, given k = 3,
Return [1,3,3,1].
Note:
Could you optimize your algorithm to use only O(k) extra space?

思路

交替使用两个数组即可。

代码

class Solution {
public:
    vector<int> getRow(int rowIndex) {
        rowIndex++;
        if(rowIndex < 1){
            return vector<int>();
        }
        vector<int> temp0(rowIndex, 0);
        vector<int> temp1(rowIndex, 0);
        vector<int>* pCurrRow = &temp0;
        vector<int>* pLastRow = &temp1;
        for(int i=0; i<rowIndex; i++){
            (*pCurrRow)[0] = 1;
            for(int col=1; col<=i; col++){
                (*pCurrRow)[col] = (*pLastRow)[col-1] + (*pLastRow)[col];
            }
            vector<int>* pTemp = pLastRow;
            pLastRow = pCurrRow;
            pCurrRow = pTemp;
        }
        return (*pLastRow);
    }
};

 

Categories
不学无术

LeetCode 38. Count and Say

题目

The count-and-say sequence is the sequence of integers beginning as follows:
1, 11, 21, 1211, 111221, ...
1 is read off as "one 1" or 11.
11 is read off as "two 1s" or 21.
21 is read off as "one 2, then one 1" or 1211.
Given an integer n, generate the nth sequence.
Note: The sequence of integers will be represented as a string.

思路

好几题没刷leetcode手痒痒,又没太多时间,就选了个Easy的题啦…这个题目确实挺简单的,统计一下前面是不是重复就行了。在句末加一个特殊符号可以避免多写几行代码的样子~

代码

class Solution {
public:
    string countAndSay(int n) {
        string nStr = "1$";
        string nextStr = "";
        while(n > 1){
            char lastChr = '$';
            int lastCount = 0;
            for(int i=0; i<nStr.size(); i++){
                if(nStr[i] == lastChr){
                    lastCount++;
                } else {
                    if(lastCount > 0){
                        string tempStr;
                        while(lastCount > 0){
                            tempStr += (lastCount%10 +'0');
                            lastCount /= 10;
                        }
                        reverse(tempStr.begin(), tempStr.end());
                        nextStr += tempStr;
                        nextStr += lastChr;
                    }
                    lastCount = 1;
                    lastChr = nStr[i];
                }
            }
            nStr = nextStr + "$";
            nextStr = "";
            n--;
        }
        return nStr.substr(0, nStr.size()-1);
    }
};

 

Categories
不学无术

LeetCode347. Top K Frequent Elements

好久不刷题了,今晚来一道。

题目

Given a non-empty array of integers, return the k most frequent elements.
For example,
Given [1,1,1,2,2,3] and k = 2, return [1,2].
Note:

  • You may assume k is always valid, 1 ≤ k ≤ number of unique elements.
  • Your algorithm’s time complexity must be better than O(n log n), where n is the array’s size.

思路

题目中明确提示要O(nlogn)复杂度了…
我的做法是先统计结果到一个map<value, count>里面,然后用一个大小为k的最小堆处理,每次从map中读取元素时根据count值判断是否要替换掉堆顶。由于STL的map是用红黑树实现的,所以时间复杂度就保证了,要再快的话就直接用hash函数吧,可以到O(n)。维护k大小的堆,算最差情况,建堆复杂度是O(k),维护复杂度是O(logk), 要维护M-k次,其中0<M<=N是map中的元素个数.
代码

struct CountVal{
    int val;
    int count;
    CountVal(int _val, int _count){
        val = _val;
        count = _count;
    }
};
bool compareObj(const CountVal& x, const CountVal& y){
    return x.count > y.count;
}
class Solution {
public:
    vector<int> topKFrequent(vector<int>& nums, int k) {
        map<int, int> countMap;
        vector<CountVal> counts;
        for(int i=0; i<nums.size(); i++){
            if(countMap.count(nums[i]) == 0)
                countMap[nums[i]] = 1;
            else
                countMap[nums[i]]++;
        }
        int count = 0;
        for(auto it=countMap.begin(); it!=countMap.end(); it++){
            CountVal item(it->first, it->second);
            if(count < k){
                counts.push_back(item);
                if(count == k-1){
                    make_heap(counts.begin(), counts.end(), compareObj);
                }
            } else {
                if (it->second > counts.front().count) {
                    pop_heap(counts.begin(), counts.end(), compareObj);
                    counts.pop_back();
                    counts.push_back(item);
                    push_heap(counts.begin(), counts.end(), compareObj);
                }
            }
            count++;
        }
        vector<int> results;
        for(auto it=counts.begin(); it!=counts.end(); it++){
            results.push_back(it->val);
        }
        return results;
    }
};

 

Categories
不学无术

操作系统中的虚拟内存

某次面试问到了这个问题,回答的不是很好,这两天仔细查了一下,先写下来。当初的问题很简单,就是“What is virtual memory in OS?”

为什么要有虚拟内存?

早期的操作系统是没有虚拟内存的概念的,在实模式(DOS下有的概念,相反的词叫“保护模式”)底下,完全没有什么分段分页或者虚拟地址的概念。这种情况下,内存的操作看起来很直白,程序中对内存的读写就是直接写实际的内存地址。这种直白的带来了很多问题,比如1)操作系统要想执行多进程就比较困难,进程的切换主要方法应该是把当前的进程写入硬盘,然后换个新的程序出来运行。因为所有程序都是直接操作内存,弄不好就崩了;2)如果程序比实际能分配的内存大,那就坑了,程序都载入不了了~
早期某些没有虚拟内存的实模式系统,内存分配的一种情况是这样
[[设备驱动][用户程序][操作系统]]
为了解决第一个问题,引入了内存抽象技术。即每个进程拥有自己的地址,引入了地址空间,操作系统分配给进程的其实是虚拟地址,这些虚拟地址在操作的时候会被转化为实际的物理地址,而这个过程对程序来说是透明的,如下图(来自《Memory management: What every driver writer needs to know》)。
Windows Virtual Memory Address
为了解决第二个问题,引入了虚拟内存技术。

程序的局部性原理

虚拟内存(Virtual Memory) 是指计算机呈现出要比实际拥有的内存大得多的内存量。因此它允许程序员编制并运行比实际系统拥有的内存大得多的程序。这使得许多大型项目也能够在具有有限内存资源的系统上实现。

虚拟内存的可用,个人认为很大程度上依赖于程序的局部性原理。局部性原理分为时间和空间上的局部性:

  1. 最近被访问的程序段在不久的将来还是很容易被访问到;
  2. 在内存中被访问的地址附近的数据在不久的将来也很容易被访问到;

也就是说,在某个特定的事件范围内,有很大比例的内存访问是集中于一小块内存范围内的,这个范围比程序声明要使用的总范围来的小(得多)。

虚拟内存技术

虚拟内存是现代操作系统普遍使用的一种技术。主要的思想很简单,拆东墙补西墙,即把感觉不怎么需要的内存块先写入磁盘,把内存中没有的但亟需使用的内存块调入内存。由于磁盘的容量比内存大很多(虽然磁盘访问的速度比内存慢很多),所以磁盘就成了放置“装不下”的内存块的大备胎。
对于一个程序来说,操作系统分配给他了可操作的地址,操作系统就像管家一样,负责管理内存、磁盘的具体调配(系统调用)。但是至于这段内存地址,实际在操作系统里是放在了内存还是磁盘还是其他什么地方里头,程序就不得而知了,大致就是下图这么个情况吧(来自维基百科)。
虚拟内存
当然这种虚拟内存是有代价的,毕竟磁盘的随机访问速度跟内存还差好几个数量级。因此系统要利用程序的局部性原理,尽量预测未来可能会频繁用到哪些内存,并把他们放入物理内存中。在Win32操作系统下,内存被按照页来分配,一个页是4KB大小。程序在执行的时候,当需要访问某个虚拟地址但是这个页面目前还在磁盘中存储的时候,系统就会产生缺页中断,缺页中断的结果是系统将自认为最近最没机会被访问的页从物理内存中换出(当然如果有空闲内存则没有这个操作),然后把现在急需的页从磁盘中调入。这里就牵扯到换页算法了,一个好的页面调度算法应当尽量减小页面换入换出的次数。
 
 

Categories
不学无术

LeetCode 331. Verify Preorder Serialization of a Binary Tree

如家网络实在太卡了,不过还好没有人劫@!$#@W…
稍微说一下思路把,这个问题就是判断某一层上的节点它的孩子会不会“溢出”,或者第一层的节点有没有完全闭合。我用一个栈来保存每一层的孩子数目(包括null):

  • 当碰到数字的时候,压一层计数为0的栈,表示新开了一层并且这一层的孩子数为0
  • 当碰到#号时,说明多了一个空孩子,当前栈顶的计数+1,并且判断当前栈顶的计数是否>=2了,如果是的话,那说明这一层已经满了,那就退栈,然后再继续给新栈顶计数+1,继续判断是否满….如此往复

由于有了#表示空节点所以层级变得明确,举个例子:一个叶节点读入两个#,当前层的计数等于2后就退栈,并且给父亲层的计数器+1(表示下一层已经结束了,父亲多了一个左/右孩子)。
代码(好久没有纯手打一遍AC了)

class Solution {
public:
    bool isValidSerialization(string preorder) {
        int size = preorder.size();
        stack<int> working;
        int i = 0;
        while(i < size){
            int end = preorder.find(',', i);
            if(end == string::npos)
                end = size;
            if(preorder[i] != '#'){
                //number
                working.push(0);
            } else {
                //'#'
                if(i == 0){
                    //The null root?!
                    if(size > 1) return false;
                    else return true;
                }
                working.top() += 1;
                while(working.size()>1 && working.top() >= 2){
                    working.pop();
                    working.top() += 1;
                }
            }
            if(working.size() == 1 && working.top() > 2)
                return false;
            i = end+1;
        }
        return (working.size()==1) && (working.top()==2);
    }
};

BTW,苏州的交通状况和路面比杭州好多了。

Categories
不学无术

LeetCode 139. Word Break

题目

Given a string s and a dictionary of words dict, determine if s can be segmented into a space-separated sequence of one or more dictionary words.
For example, given
s = "leetcode",
dict = ["leet", "code"].
Return true because "leetcode" can be segmented as "leet code".

思路1

写的第一个版本是超时的,但是思路应当是正确的,反正就是动态规划,但我用的是递归方法。
就是如果找到s[start,end]是单词表中的单词时,有两种处理,一种是算入然后往后走;另一种是忽略这次匹配成功,继续往后寻找。
代码大概这样:

class Solution {
public:
	int findNextWord(const string& s, int start, int curr, unordered_set<string>& wordDict) {
		//Find the end position of next possible word, or return -1
		string word = s.substr(start, curr - start + 1);
		for (int i = curr + 1; i < s.size(); i++) {
			word += s[i];
			if (wordDict.count(word) > 0) {
				return i;
			}
		}
		return -1;
	}
	bool parseSeq(const string& s, unordered_set<string>& wordDict, int start, int end, map<int, bool>& cache) {
		//Judge s[start...end]
		if (end >= s.size())
			return false;
		if (cache.count(start) > 1)
			return cache[start];
		string word = s.substr(start, end - start + 1);
		if (wordDict.count(word) > 0) {
			bool withThisWord = parseSeq(s, wordDict, end + 1, end + 1, cache);
			if (end + 1 == s.size() || withThisWord) {
				cache[start] = true;
				return true;
			}
		}
		int nextPos = findNextWord(s, start, end, wordDict);
		if (-1 != nextPos) {
			bool appendWord = parseSeq(s, wordDict, start, nextPos, cache);
			if (appendWord) {
				cache[start] = true;
				return true;
			}
		}
		cache[start] = false;
		return false;
	}
	bool wordBreak(string s, unordered_set<string>& wordDict) {
		map<int, bool> cache;
		return parseSeq(s, wordDict, 0, 0, cache);
	}
};

但是如果这里出现了超级多的单字,比如字符串是

aaaaaaaaaaaaaaaaaaaaaaa

然后字典是

{a,aa,aaa,aaaa}

这就搞笑了,得匹配死,所以肯定是超时。

思路2

这个是讨论组里看来的,突然发现一般来说DP递归能做的,就可以从后往前用非递归的方法写。对于这个问题来说,从后往前并不像做LCS那样时要完全倒着写。举个例子,有字符串str=thisisawesome 和字典{this, is, a, we, some, awesome} 的时候,可以用一个缓存cache来记录是否在str[i]位置有单词结束,比如str[3]位置就是”this”这个词结束的地方。当顺着继续搜索的时候,往前查找缓存记录,可以知道如果cache[i]=true的话,那才有可能从i这个位置往后到当前位置组成新单词,接着去查找字典看看有没有这个单词,如果没有的话,就得继续往前找到新的可用的开头i。这里加黑的“才有可能”是比较关键的。
代码大概是这样

bool wordBreak(string s, unordered_set<string>& wordDict) {
		if (wordDict.empty())
			return false;
		vector<bool> cache(s.size() + 1, false); //If a word ends at i, cache[i] = true
		cache[0] = true;
		for (int i = 0; i <= s.size(); i++) {
			for (int j = i - 1; j >= 0; j--) {
				if (cache[j]) {
					//Look backward and start at the end place of previous word
					string word = s.substr(j, i - j); // s[j...i-1]
					if (wordDict.find(word) != wordDict.end()) {
						cache[i] = true;
						break;
					}
				}
			}
		}
		return cache[s.size()];
	}

这里面向前迭代不断寻找cache[i]并尝试的情况,对应于之前思路1里面找到可用单词后的决定是否使用的分支情况。但是由于减少了一大堆递归调用,所以实际情况下的复杂度降低了不少。

Categories
不学无术

LeetCode 151. Reverse Words in a String

题目

Given an input string, reverse the string word by word.
For example,
Given s = “the sky is blue“,
return “blue is sky the“.
Update (2015-02-12):
For C programmers: Try to solve it in-place in O(1) space.

思路

一!定!要!考!虑!各!种!情!况!比如

" "//一个空格
"   What am   I doing     "

然后这种倒转问题的原地处理思路应该都明白的,先把整个字符串倒转一遍,然后按照单词再倒转回来,拿”the sky is blue”为例:

  1. “eulb si yks eht“
  2. “blue si yks eht”
  3. “blue is yks eht”
  4. …
  5. “blue is sky the”

代码

class Solution {
public:
	void reverseWordsSub(string &s, int start, int end) {
		//[start,end]
		char c;
		int count = 0;
		const int maxInd = (end - start - 1) / 2 + start;
		for (int i = start; i <= maxInd; i++) {
			c = s[i];
			s[i] = s[end - count];
			s[end - count] = c;
			count++;
		}
	}
	void clearStr(string &s) {
		//Clear unnecessary spaces
		int validIndex = -1;
		bool spaceFlag = false;
		for (int i = 0; i<s.size(); i++) {
			if (s[i] != ' ') {
				s[++validIndex] = s[i];
				spaceFlag = false;
			}
			else if (!spaceFlag) {
				if (validIndex < 0) {
					continue;
				}
				spaceFlag = true;
				s[++validIndex] = s[i];
			}
		}
		if (spaceFlag)
			validIndex--;
		if (validIndex < 0)
			s = "";
		else
			s = s.substr(0, validIndex + 1);
	}
	void reverseWords(string &s) {
		clearStr(s);
		int strSz = s.size();
		reverseWordsSub(s, 0, strSz - 1);
		int start = 0;
		for (int i = 0; i<strSz; i++) {
			if (s[i] == ' ') {
				reverseWordsSub(s, start, i - 1);
				start = i + 1;
			}
		}
		reverseWordsSub(s, start, strSz - 1);
	}
};

 

Categories
不学无术

LeetCode 9. Palindrome Number

题目

Determine whether an integer is a palindrome. Do this without extra space.

Some hints:Could negative integers be palindromes? (ie, -1)
If you are thinking of converting the integer to string, note the restriction of using extra space.
You could also try reversing an integer. However, if you have solved the problem “Reverse Integer”, you know that the reversed integer might overflow. How would you handle such case?
There is a more generic way of solving this problem.

思路

首先要注意,负数不是回文数。
然后题目中要求不能使用额外的空间,我的理解是O(1)空间复杂度,即不能用字符串来存储?

代码

我这个解决方案不知道是不是违规了,如果再苛刻一点,那就是每次测试当前数字的位数(连续除10),然后用除法和模10判断首位和末尾的数字。

class Solution {
public:
    bool isPalindrome(int x) {
        if(x < 0)
            return false;
        int cover = 1;
        int x_copy = x;
        while(x_copy/10 > 0){
            x_copy /= 10;
            cover *= 10;
        }
        while(cover >= 10){
            int front = x/cover;
            int end = x%10;
            if(front != end){
                return false;
            }
            x -= front * cover;
            x /= 10;
            cover /= 100;
        }
        return true;
    }
};