跟着书里的讲解,跟着做了一遍该项目:

首先明白注册账户的需求

账号的lD和Email地址都可以用来唯一地标识某个用户,而显示名称则用来显示在页面下,方便浏览。注册的时候用户还需要输入两次密码,以确保没有输错,系统则需要负责检查ID和email的唯一性,验证两次输入的密码是否一致,验证码是由系统随机生成的只能由肉眼识别其内容的图片,若输入正确的验证码信息,系统则会进行检查,如果验证码错误。系统会生成并返回新的验证码。所有检查

都没问题了,系统就会生成一个激活链接,并发送到用户的邮箱。单击激活链接后,账户就被激活了,这时账户注册完成,用户可以进行登录。对于一个账户注册服务,还需要考虑一些安全因素,例如,需要在服务器端密文地保存密码,检查密码的强弱程度,更进一步则需要考虑验证码的失效时间,激活链接的失效时间等等。

需求用例:

主要场景:

1、用户访问注册页面

2、系统随机生成验证码图片

3、用户输入ID、Email等注册信息

4、输入验证码

5、提交注册请求

6、系统检查验证码、检查ID的唯一性,检查邮箱是否已被注册、密码和确认密码是否一致

7、系统保存未激活的账户信息

8、系统生成激活连接,发送给用户邮箱

9、用户打开邮箱,访问激活链接

10、系统解析激活连接,激活相关用户

11、用户使用ID和密码登陆

扩展场景:

4a:用户无法看清验证码,请求重新生成    1、跳转到2

6a:系统检测到用户输入的验证码有误        1、提示验证码有错,2、跳转到2

6b:检测到 ID已被注册,邮箱,密码有误    1、提示错误信息,2、跳转到2

从上面可以看出该服务有几个接口:生成验证码图片、处理注册请求、激活账户以及处理登陆请求。

接口结构:

acountService类:

generateCaptchaKey()

generateCaptchaImage(captchakey:string)

signUp(signUpRequest:SignUpRequest):接收对象,进行验证。如果验证正确。则创建一个末被激活的账户,同时在后台也需要发送一封带有激活链接的邮件。

activate(activationNumber:string):方法接收一个激活码,查找时应的账户进行激活

login(id:string,password:string)

signUpRequest:包含用户的注册信息,表单信息:id、Email、displayName、password、comfirmpassword、captchaKey、captchaValue。

generateCaptchaKey()的简单解释就是验证码,每个captcha都需要有一个key ,根据这个key ,系统才能得到对应的验证码图片以及实际值。因此,generateCaptchaImage会生成一个captchakey使用这个key再调用generateCaptchaImage方法就能得到验证码图片。验证码的key以及验证码图片被传送到客户端,用户通过肉眼识别再输人验证码的值,伴随着key再传送到服务器端验证,服务器端就可以通过这个key查到正确的验证码值,井与客户端传过来的值进行比对验证。

模块划分:

com.hust.silence.accout.service:系统的核心,它封装了所有下层细节,对外暴露简单的接日,这实际上是一个Facade模式。

com.hust.silence.accout.web:该模块包含所有与web相关的内容,包括jsp等,直接依赖于service模块

com.hust.silence.accout.persist:处理账户信息的持久化,包括增、删、改、查等,根据实现,可以基于数据库或者文件

com.hust.silence.accout.captcha:处理验证码的key生成、图片生成以及验证等

com.hust.silence.accout.email: 处理邮件服务的配置,激活邮件的编写和发送等

配置pom.xml

加入需要的各种spring framework的模块,Greenmail是开源的邮件服务套件,Javax.mail为实现发送的一个类库。从上面的信息我们可以知道,该项目时是com.hust.silence的一个account项目,项目里有一个模块为account-email。

实现Email模块:

account-email只有一个很简单的接口。

public interface AccountEmailService {
void sendMail(String to, String subject, String htmlText) throws AccountEmailException;
} public class AccountEmailServiceImpl implements AccountEmailService{
private JavaMailSender javaMailSender;
private String systemEmail; public void sendMail(String to, String subject, String htmlText)
throws AccountEmailException {
// TODO Auto-generated method stub
try {
MimeMessage msg = javaMailSender.createMimeMessage();
MimeMessageHelper msgHelper = new MimeMessageHelper(msg);
msgHelper.setFrom(systemEmail);
msgHelper.setTo(to);
msgHelper.setSubject(subject);
msgHelper.setText(htmlText);
javaMailSender.send(msg);
} catch (Exception e) {
// TODO: handle exception
throw new AccountEmailException("fail to send mail",e);
}
}
//实现依赖注入
public JavaMailSender getJavaMailSender(){
return javaMailSender;
}
public void setJavaMailSender(JavaMailSender javaMailSender){
this.javaMailSender = javaMailSender;
}
public String getSystemEmail(){
return systemEmail;
}
public void setSystememail(String systemEmail){
this.systemEmail = systemEmail;
}
} public class AccountEmailException extends Exception {
/**
*
*/
private static final long serialVersionUID = 6514881539290222459L;
public AccountEmailException(String message) {
super(message);
}
public AccountEmailException(String message, Throwable throwable){
super(message, throwable);
}
}

配置文件:

id="propertyConfigurer":这是springframework用来帮助载入properties文件的组件,代码中表示从classpath的根目录下载入名为account-email.properties文件中的属性。

id="javaMailSender":定义邮件服务器的一些配置.包括协议、端口、主机,用户名、密码,是否需要认证等属性。这段配置还使用了propertyConfigurer的属性引用,比如host的值为$ { email.host }。之前定义的propertyConfigurer作用就在于此、可以将邮件服务器相关的配置分离到外部的properties文件中,比如可以定义这样一个properties文件。配置javaMailSender使用163:

account-email.properties(在src/test/resources文件夹里):

email.protocol=smtp

email.host=smtp.163.com

email.port=25

email.username=test@163.com

email.password=password

email.auth=true

email.systemEmail=test@163.com

测试:

只需要测试一个sendMail()接口,这个就需要准备properties文件,配置并启用一个测试使用的邮件服务器,准备好后,就调用该接口实现邮件发送,然后检查是否发送成功,关闭测试邮件服务器。具体代码:

public class AccountEmailServiceTest {
private GreenMail greenMail; @Before
public void startMailServer() throws Exception{
greenMail = new GreenMail(ServerSetup.SMTP);
greenMail.setUser("1219611916@qq.com", "silence");
greenMail.start();
} @Test
public void testSendMail() throws Exception{
//根据account。xml创建一个spring framework的ApplicationContext
ApplicationContext ctx = new ClassPathXmlApplicationContext("account_email.xml");
//从ctx中获取需要测试的ID为accountEmailService的bean并转换成AccountEmailService接口,
//针对接口的测试是最好的单元测试的实现
AccountEmailService accout = (AccountEmailService)ctx.getBean("accountEmailService");
String subject = "Test Subject";
String htmlText = "<h3>test</h3>";
accout.sendMail("1219611916@qq.com", subject, htmlText);
greenMail.waitForIncomingEmail(2000, 1);
Message[] mags = greenMail.getReceivedMessages();
assertEquals(1,mags.length);
assertEquals(subject, mags[0].getSubject());
assertEquals(htmlText, GreenMailUtil.getBody(mags[0]).trim());
} @After
public void stopMailServer() throws Exception{
greenMail.stop();
}
}

实现persist模块:

该模块负责账户数据的持久化,以XML文件的形式保存账户数据,井支持账户的创建、读取、更新、删除等操作。

配置代码:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.hust.silence.account</groupId>
<artifactId>account-persist</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>account-persist</name>
<url>http://maven.apache.org</url> <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<dom4j.version>1.6.1</dom4j.version>
<springframework.version>2.5.6</springframework.version>
</properties> <dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.7</version>
<scope>test</test>
</dependency>
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>${dom4j.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${springframework.version}</version>
</dependency>
</dependencies> <build>
<testResources>
<testResource>
<directory>src/test/resources</directory>
<filtering>true</filtering>
</testResource>
</testResources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<configuration>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
</project>  

dom4j是支持XML操作的,build元素包含testresource是开启资源过滤的,在单元测试中用到。

具体代码:

public interface AccountPersistService {
Account createAccount(Account account) throws AccountPersistException;
Account readAccount(String id) throws AccountPersistException;
Account updateAccount(Account account) throws AccountPersistException;
void deleteAccount(String id) throws AccountPersistException;
} public class AccountPersistServiceImpl
implements AccountPersistService
{
private static final String ELEMENT_ROOT = "account-persist";
private static final String ELEMENT_ACCOUNTS = "accounts";
private static final String ELEMENT_ACCOUNT = "account";
private static final String ELEMENT_ACCOUNT_ID = "id";
private static final String ELEMENT_ACCOUNT_NAME = "name";
private static final String ELEMENT_ACCOUNT_EMAIL = "email";
private static final String ELEMENT_ACCOUNT_PASSWORD = "password";
private static final String ELEMENT_ACCOUNT_ACTIVATED = "activated"; private String file; private SAXReader reader = new SAXReader(); public String getFile()
{
return file;
} public void setFile( String file )
{
this.file = file;
} public Account createAccount( Account account )
throws AccountPersistException
{
Document doc = readDocument(); Element accountsEle = doc.getRootElement().element(ELEMENT_ACCOUNTS); accountsEle.add( buildAccountElement( account ) ); writeDocument( doc ); return account;
} @SuppressWarnings("unchecked")
public void deleteAccount( String id )
throws AccountPersistException
{
Document doc = readDocument(); Element accountsEle = doc.getRootElement().element( ELEMENT_ACCOUNTS ); for ( Element accountEle : (List<Element>) accountsEle.elements() )
{
if ( accountEle.elementText( ELEMENT_ACCOUNT_ID ).equals( id ) )
{
accountEle.detach(); writeDocument( doc ); return;
}
}
} @SuppressWarnings("unchecked")
public Account readAccount( String id )
throws AccountPersistException
{
Document doc = readDocument(); Element accountsEle = doc.getRootElement().element( ELEMENT_ACCOUNTS ); for ( Element accountEle : (List<Element>) accountsEle.elements() )
{
if ( accountEle.elementText( ELEMENT_ACCOUNT_ID ).equals( id ) )
{
return buildAccount( accountEle );
}
} return null;
} public Account updateAccount( Account account )
throws AccountPersistException
{
if ( readAccount( account.getId() ) != null )
{
deleteAccount( account.getId() ); return createAccount ( account );
} return null;
} private Account buildAccount( Element element )
{
Account account = new Account(); account.setId( element.elementText( ELEMENT_ACCOUNT_ID ) );
account.setName( element.elementText( ELEMENT_ACCOUNT_NAME ) );
account.setEmail( element.elementText( ELEMENT_ACCOUNT_EMAIL ) );
account.setPassword( element.elementText( ELEMENT_ACCOUNT_PASSWORD ) );
account.setActivated( ( "true".equals( element.elementText( ELEMENT_ACCOUNT_ACTIVATED ) ) ? true : false ) ); return account;
} private Element buildAccountElement( Account account )
{
Element element = DocumentFactory.getInstance().createElement( ELEMENT_ACCOUNT ); element.addElement( ELEMENT_ACCOUNT_ID ).setText( account.getId() );
element.addElement( ELEMENT_ACCOUNT_NAME ).setText( account.getName() );
element.addElement( ELEMENT_ACCOUNT_EMAIL ).setText( account.getEmail() );
element.addElement( ELEMENT_ACCOUNT_PASSWORD ).setText( account.getPassword() );
element.addElement( ELEMENT_ACCOUNT_ACTIVATED ).setText( account.isActivated() ? "true" : "false" ); return element;
} private Document readDocument()
throws AccountPersistException
{
File dataFile = new File( file ); if( !dataFile.exists() )
{
dataFile.getParentFile().mkdirs(); Document doc = DocumentFactory.getInstance().createDocument(); Element rootEle = doc.addElement( ELEMENT_ROOT ); rootEle.addElement( ELEMENT_ACCOUNTS ); writeDocument( doc );
} try
{
return reader.read( new File( file ) );
}
catch ( DocumentException e )
{
throw new AccountPersistException( "Unable to read persist data xml", e );
}
} private void writeDocument( Document doc )
throws AccountPersistException
{
Writer out = null; try
{
out = new OutputStreamWriter( new FileOutputStream( file ), "utf-8" ); XMLWriter writer = new XMLWriter( out, OutputFormat.createPrettyPrint() ); writer.write( doc );
}
catch ( IOException e )
{
throw new AccountPersistException( "Unable to write persist data xml", e );
}
finally
{
try
{
if ( out != null)
{
out.close();
}
}
catch ( IOException e )
{
throw new AccountPersistException( "Unable to close persist data xml writer", e );
}
}
}
}

Account是一个简单的类:对变量的读取和设置

private String id;
private String name;
private String email;
private String password;
private boolean activated; 

该测试用例遵守了测试接口而不测试实现这一原则:也就是说,测试代码不能引用实现类,由于测试是从接口用户的角度编写的,这样就能保证接口的用户无须知晓接口的实现细节,既保证了代码的解藕,也促进了代码的设计。

测试代码:这里只给出了读取readAccount的test

public class AccountPersistServiceTest {
private AccountPersistService service;
@Before
public void prepare() throws Exception{
File persistDataFile = new File("target/persist-classes/persist_data.xml");
if(persistDataFile.exists()){
persistDataFile.delete();
}
ApplicationContext ctx = new ClassPathXmlApplicationContext("account_persist.xml");
service = (AccountPersistService) ctx.getBean("accountPersistService");
Account account = new Account();
account.setId("ww");
account.setName("wwss");
account.setEmail("16@qq.com");
account.setPassword("####");
account.setActivated(true);
service.createAccount(account);
}
@Test
public void testReadAccount() throws Exception{
Account account = service.readAccount("ww");
assertEquals("ww",account.getId());
assertEquals("wwss",account.getName());
assertEquals("126@qq.com",account.getEmail());
assertEquals("####",account.getPassword());
assertTrue(account.isActivated());
} }

  想要将上面的代码单个模块运行成功,还需要给出相应的配置文件,xml文件放在src/main/resources下,properties文件放在src/test/resources。

  尤其是这里只给出了相应的两个模块的代码,有兴趣的可以继续写完成。

account_email.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"> <bean id="propertyConfigurer"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property
name="location"
value="classpath:account_email.properties"/>
</bean> <bean id="javaMailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">
<property name="protocol" value="${email.protocol}"></property>
<property name="host" value="${email.host}"></property>
<property name="port" value="${email.port}"></property>
<property name="username" value="${email.username }"></property>
<property name="password" value="${email.password }"></property>
<property name="javaMailProperties" >
<props>
<prop key="mail.${email.protocol}.auth">${email.auth}</prop>
</props>
</property>
</bean> <bean id="accountEmailService"
class="com.hust.silence.account.email.AccountEmailServiceImpl">
<property name="javaMailSender" ref="javaMailSender"></property>
<property name="systemEmail" value="${email.systemEmail}"></property>
</bean> </beans>

  account_email.properties:

email.protocol=smtp
email.host="127.0.0.1"
email.port="25"
email.username=16@qq.com
email.password=####
email.auth=true
email.systemEmail=16@qq.com

  

maven小项目注册服务(一)--email和persist模块的更多相关文章

  1. maven小项目注册服务(三)--web模块

    java的web应用打包方式一般为war它和jar的区别就是包含了更多的资源和文件,如JSP文件,静态资源文件,servlet等等.war包的核心就WEB-INF文件夹下必须有一个web.xml 的配 ...

  2. maven小项目注册服务(二)--captcha模块

    验证码生成模块,配置信息基本和前面的模块一样.account-captcha需要提供的服务是生成随机的验证码主键,然后用户可以使用这个主键要求服务生成一个验证码图片,这个图片对应的值应该是随机的,最后 ...

  3. idea结合maven小项目

    整体构造 (修改 POM 文件 )parent <?xml version="1.0" encoding="UTF-8"?> <project ...

  4. Spring Cloud 如何使用Eureka注册服务 4.2.2

    要使用Eureka实现服务发现,需要项目中包含Eureka的服务端发现组件以及客户端发现组件. 搭建Maven父工程 创建一个Maven父工程xcservice-springcloud,并在工程的po ...

  5. 使用 Maven 管理项目

    最近的练手项目使用的是 Maven 在管理项目,在使用 Maven 管理项目时,三层的开发时分模块开发的,parent-dao-service-web,所有的spring+struts + Hiber ...

  6. Dubbo+Zookeeper+SpringMVC+Maven整合实现微服务项目

    互联网的发展,网站应用的规模不断扩大,常规的垂直应用架构已无法应对,分布式服务架构以及流动计算架构势在必行,Dubbo是一个分布式服务框架,在这种情况下诞生的.现在核心业务抽取出来,作为独立的服务,使 ...

  7. Nacos笔记01——使用Nacos作为SpringCloud项目的服务注册中心

    前言 刚学SpringCloud时使用eureka作为服务注册中心,随着网飞公司eureka2.x不再更新,以及最近在公司实习接触到的SpringCloud项目是使用Nacos来做服务注册中心的,所以 ...

  8. 手把手教你写一个windows服务 【基于.net】 附实用小工具{注册服务/开启服务/停止服务/删除服务}

    1,本文适用范围 语言:.net 服务类型:windows服务,隔一段时间执行 2,服务搭建: 1,在vs中创建 console程序 2,在console项目所在类库右键 添加-新建项-选择Windo ...

  9. spring+springmvc+hibernate架构、maven分模块开发样例小项目案例

    maven分模块开发样例小项目案例 spring+springmvc+hibernate架构 以用户管理做測试,分dao,sevices,web层,分模块开发測试!因时间关系.仅仅測查询成功.其它的准 ...

随机推荐

  1. 【转载】MySQL 日志 undo | redo

    本文是介绍MySQL数据库InnoDB存储引擎重做日志漫游 00 – Undo LogUndo Log 是为了实现事务的原子性,在MySQL数据库InnoDB存储引擎中,还用Undo Log来实现多版 ...

  2. plateform_driver_register和plateform_device_register区别

    设备与驱动的两种绑定方式:在设备注册时进行绑定及在驱动注册时进行绑定. 以一个USB设备为例,有两种情形: (1)先插上USB设备并挂到总线中,然后在安装USB驱动程序过程中从总线上遍历各个设备,看驱 ...

  3. 在SQL SErver中实现数组功能

    T-SQL象数组一样处理字符串.分割字符串    在日常的编程过程中,数组是要经常使用到的.在利用SQL对数据库进行操作时,有时就想在SQL使用数组,比如将1,2,3,4,5拆分成数组.可惜的是在T- ...

  4. C# 数据结构--排序[上]

    概述 看了几天的排序内容,现在和大家分享一些常见的排序方法. 啥是排序? 个人理解的排序:通过对数组中的值进行对比,交换位置最终得到一个有序的数组.排序分为内存排序和外部排序.本次分享排序方法都为内存 ...

  5. Java多线程——<六>更方便的线程

    一.概述 标题很抽象,什么叫更方便?更是相比谁来说的呢? 原来,我们定义任务,都是实现自Runnable或者Callable接口,但是这样必然需要你将新定义的任务附着给线程,然后再调用线程启动.在不考 ...

  6. iframe嵌入其他网站,如何自适应高度

    终于有一周时间,工作不那么忙了,腾出手来总结下工作过程中学到的知识. 每天遇到新问题,解决新问题,但是却很少有时间去仔细研究下,或者总结下.攒的多了,就得从头捋一遍. 说下iframe自适应高度: 搜 ...

  7. uva 11489

    简单博弈 #include <cstdio> #include <cstdlib> #include <cmath> #include <map> #i ...

  8. jQuery中的&& ||

    jQuery1.2.6 clean方法中有这么一段第一眼看去会让人晕掉的方法.完全不知其所言. “||, && 可以这样用?”,“这段东西最终返回的是个什么对象啊?” // Trim ...

  9. C++中的const关键字

    http://blog.csdn.net/eric_jo/article/details/4138548 C++中的const关键字的用法非常灵活,而使用const将大大改善程序的健壮性,本人根据各方 ...

  10. python参考手册--第1章python简介

    1.if __name__ == '__main__': 直接运行myModel.py时,当前模块的名字是main:import myModel然后运行的时候当前模块的名字是myModel. 2.ev ...