• maven小项目注册服务(一)--email和persist模块


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

    首先明白注册账户的需求

           账号的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
    

      

  • 相关阅读:
    图形信息与文字信息的区别
    逻辑后承:从语句到图形
    面向计算机科学的非经典逻辑
    安装ubuntu10.10后,如何配置一个Apache+MySQL+PHP环境
    如何查看RPG程序从何处编译
    向远程系统提交命令
    如何查看未备份成功的文件列表
    如何显示查询的调试信息
    如何检查谁删除了文件
    如何在SQL/400查询指令结果的最后一行插入合计
  • 原文地址:https://www.cnblogs.com/silence-hust/p/4214273.html
Copyright © 2020-2023  润新知