工作之余搭建的电商平台,现已存在的功能有:
1)用户注册登陆
2)购物车功能(未登录购物车商品保存cookie,登陆保存redis)
3)订单填写确认功能
4)订单状态查询功能


技术实现:
使用SpringMVC架构 maven包管理
1)SpringSecurity/Validation完成用户登陆注册验证及反馈
2)购物车由于更新频繁,使用二级缓存redis来保存用户登陆后的购物车信息,未登录状态下的购物车信息保存到浏览器cookie,中间登陆会把cookie中的购物车更新到redis并清除cookie
3)数据持久化经hibernate到MySQL
4)React构建部分页面(譬如用户登录后用户信息目前保存到session中,这一块在前端用jsp实现,故这部分页面是jsp+react混用)

主要用到的技术就是上边这样,当然像jquery/bootstrap/css这些项目中肯定也是必须的

已提交到github,地址


一、maven的pom.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/maven-v4_0_0.xsd">

<modelVersion>4.0.0</modelVersion>
<groupId>com.git.postgraduate</groupId>
<artifactId>bookstore</artifactId>
<packaging>war</packaging>
<version>0.0.1-SNAPSHOT</version>
<name>bookstore Maven Webapp</name>
<url>http://maven.apache.org</url>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.spring.platform</groupId>
<artifactId>platform-bom</artifactId>
<version>Brussels-RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<dependencies>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
</dependency>

<!-- Hibernate -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
</dependency>

<!-- DBCP connection pool -->
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
</dependency>
<dependency>
<groupId>commons-pool</groupId>
<artifactId>commons-pool</artifactId>
</dependency>

<!-- MySQL -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>

<!-- Servlet -->
<!-- https://mvnrepository.com/artifact/javax.servlet/servlet-api -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
</dependency>

<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>

<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>


<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</dependency>

<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
</dependency>

<dependency>
<groupId>commons-validator</groupId>
<artifactId>commons-validator</artifactId>
<version>1.5.0</version>
</dependency>

<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
</dependency>

<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
</dependencies>

<build>
<finalName>bookstore</finalName>
</build>
</project>

以上为引入的项目依赖包
先对其中几个点略作介绍
1、dependencyManagement 负责管理包的version,下边的dependency不需要再填写version
2、spring的核心部分:webmvc/orm
3、hibernate部分:hibernate-core/hibernate-entitymanager
4、mysql部分:mysql-connector-java
5、jsp/servlet部分:servlet-api/jsp-api/jstl
6、security部分:spring-security-web/spring-security-config
7.validation部分:commons-validator
8.redis部分:spring-data-redis/jedis


二、登录模块
先看配置部分:
1、security-context.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans>

<authentication-manager alias="authenticationManager">
<authentication-provider user-service-ref="userDetailsServiceImpl">
<password-encoder ref="encoder"></password-encoder>
</authentication-provider>
</authentication-manager>

<beans:bean id="userDetailsServiceImpl" class="git.com.postgraduate.bookstore.service.UserDetailsServiceImpl"></beans:bean>

<beans:bean id="encoder"
class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder">

<beans:constructor-arg name="strength" value="11"/>
</beans:bean>
</beans:beans>

authenticationManager作为认证管理中心,以userDetailsServiceImpl为认证来源,密码编码处理用BCrypt处理,防止密码以明文形式传递

来看下userDetailsServiceImpl:

package git.com.postgraduate.bookstore.service;
//packages dependency ignore
@Service
public class UserDetailsServiceImpl implements UserDetailsService {

@Autowired
private AccountDAO accountDAO;

@Transactional(readOnly= true)
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Account account = accountDAO.findAccount(username);
System.out.println("Account=" + account);

if(account == null) {
throw new UsernameNotFoundException("User" + username + "was not found in the database");
}

//EMPLOYEE MANAGER
String role = account.getUserRole();

List<GrantedAuthority> grantList = new ArrayList<GrantedAuthority>();

// ROLE_EMPLOYEE ROLE_MANAGER
GrantedAuthority authority = new SimpleGrantedAuthority("ROLE_" + role);
grantList.add(authority);

boolean enabled = account.isActive();
boolean accountNonExpired = true;
boolean credentialsNonExpired = true;
boolean accountNonLocked = true;

UserDetails userDetails = (UserDetails)new User(account.getUserName(), account.getPassword(), enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, grantList);
return userDetails;
}

}

大致意思是:根据用户提供的userName从user数据库中取出该用户名所匹配的用户真实信息(这里以userName作为primary key,唯一),将user 真实信息(username/password/userRole/…)返回给认证管理中心,注意,该类实现了UserDetailsService接口,该接口属于springSecurity

除了xml配置security外,还有另外一个java类作为config类(当然可以完全用xml配置或用java类配置),该类主要作用是设置哪些页面访问需要权限formLogin登陆验证成功或失败页面如何跳转

package git.com.postgraduate.bookstore.config;

@EnableWebSecurity
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

/*授权相关的在security-context.xml中已配置
* 包括provider和password encoder
* */

@Override
protected void configure(HttpSecurity http) throws Exception {
//TODO
http.csrf().disable();

//the pages requires login as EMPLOYEE or MANAGER.
//if not login, it will redirect to /login page
http.authorizeRequests().antMatchers("/orderList","/order","/accountInfo")
.access("hasRole('ROLE_EMPLOYEE', 'ROLE_MANAGER')");

http.authorizeRequests().antMatchers("/product").access("hasRole('RILE_MANAGER')");

//when the user has logged in as XX.
//But access a page that requires role YY,
//AccessDeniedException will throw
http.authorizeRequests().and().exceptionHandling().accessDeniedPage("/403");

http
.authorizeRequests()
.and()
.formLogin()
.loginPage("/login")
.loginProcessingUrl("/request_for_login")
.defaultSuccessUrl("/findbook")
.failureUrl("/login?error")
.usernameParameter("userName")
.passwordParameter("password")
.and().logout().logoutUrl("/logout").logoutSuccessUrl("/login?logout");
}
}

专门解释下用户登陆模块:

.loginProcessingUrl(“/request_for_login”) —form提交时的action path,即某表单提交的路径是/request_for_login时拦截到此进行验证

.defaultSuccessUrl(“/findbook”) — 验证通过后默认的跳转页面

.failureUrl(“/login?error”) — 验证失败后的跳转页面,url后带error,

.usernameParameter(“userName”)
.passwordParameter(“password”) —form提交过来的username和password与认证管理中心的用户真实信息进行匹配,从而验证通过或失败?(此处有待考证)

.logout().logoutUrl(“/logout”).logoutSuccessUrl(“/login?logout”) — 登出后跳转到哪个页面


另外还有一个service,算是一个工具类,提供查询当前已通过用户验证的用户信息(这里主要是为了将已登录过的用户信息显示到前端)

package git.com.postgraduate.bookstore.service;

@Service
public class SecurityServiceImpl implements SecurityService {

public String findLoggedUsername() {
String username = SecurityContextHolder.getContext().getAuthentication().getName();
if(!username.equals("anonymousUser"))
return username;
return null;

}
}

OK,到这里,基本上用户的登陆模块基本完成了,还有已登录用户的信息跟踪功能(SecurityServiceImpl )

当登陆失败后返回登陆页面,如何显示错误信息呢?
看一下:

@RequestMapping(value={"/login"}, method= RequestMethod.GET)
public String login(Model model,String error, String logout) {
if(error != null)
model.addAttribute("error", "Your username and password is invalid");
if(logout != null)
model.addAttribute("message", "You have been logged out successfully");
return "login";
}

ok,之前我们在WebSecurityConfig 配置了登陆失败和登出后跳转到/login,并分别带过来error和logout,这样就会将状态信息放入model传到前端来显示到底是登陆失败还是登出成功啦


二、注册模块
来来来,看一下注册模块,学习的过程是不是让人兴奋?哈哈 当然我把各功能拆开来分析的,很多细节没有分析到或者解析的不对,欢迎斧正。

这里的注册功能我就走正常的MVC模式了,request先mapping到controller(其实登陆模块主要是用了springSecurity来处理的,并没有走MVC模式

先看controller

package git.com.postgraduate.bookstore.controller;

@Controller
public class SecurityController {

@Autowired
private AccountService accountService;

@Autowired
private AccountValidator accountValidator;

@RequestMapping(value={"/registration"}, method= RequestMethod.GET)
public String Registration(Model model) {
model.addAttribute("accountForm", new Account());

return "registration";
}

@RequestMapping(value={"/registration"}, method= RequestMethod.POST)
public String registration(@ModelAttribute("accountForm") Account accountForm, BindingResult bindingResult, Model model) {
accountValidator.validate(accountForm, bindingResult);

if(bindingResult.hasErrors()) {
return "registration";
}

accountService.save(accountForm);

//securityService.autologin(accountForm.getUserName(), accountForm.getPassword());

return "redirect:login";

}
}

ok, 用户发送/registration get请求,我将注册页面返回

用户填写后 发送/registration post请求,我在这里要验证(validation)用户的填写是否符合我的要求:
accountValidator.validate(accountForm, bindingResult);

来看看 accountValidator中的validate():

package git.com.postgraduate.bookstore.validator;

@Component
public class AccountValidator implements Validator {
@Autowired
AccountService accountService;

public boolean supports(Class<?> aClass) {
return Account.class.equals(aClass);
}

public void validate(Object o, Errors errors) {
Account account = (Account) o;

ValidationUtils.rejectIfEmptyOrWhitespace(errors, "userName", "NotEmpty.accountForm.name");
if(account.getUserName().length() < 6 || account.getUserName().length() > 32) {
errors.rejectValue("userName", "Size.accountForm.username");
}
if(accountService.findByUsername(account.getUserName()) != null) {
errors.rejectValue("userName", "Duplicate.accountForm.username");
}

ValidationUtils.rejectIfEmptyOrWhitespace(errors, "password", "NotEmpty.accountForm.password");
if(account.getPassword().length() < 8 || account.getPassword().length() > 32) {
errors.rejectValue("password", "Size.accountForm.password");
}

if(!account.getPasswordConfirm().equals(account.getPassword())) {
errors.rejectValue("password", "Diff.accountForm.passwordConfirm");
}

}

}

validate(object o, errors errors) { } 传入两个参数,一个是要验证的对象,对象所有属性验证假如有error便会将错误信息(哪个field有什么样的错误)返回给errors对象,在controller里就是bindingResult了,判断bindingResult.hasErrors(),有则返回注册页面并显示错误,无则注册信息,并重定向到登录页面。这里涉及到前端页面如何展示错误信息,不再赘述,可以看github中的页面


ok ,这块还是分开写吧,下一次写一下购物车功能的实现,做过的东西记性不好的话 要拿来经常总结的,下次假如用到了,可以用这个临时大脑帮忙回忆一下。

更多相关文章

  1. 在创建多对多关系后,Sequelize Node.js新功能无法正常工作
  2. 有没有办法阻止使用类似Firebug的工具在页面中编辑HTML和CSS内容
  3. 屏蔽标签的href跳转功能
  4. 与同一页面上的两个jquery datepickers冲突
  5. Vue自定义指令实现checkbox全选功能
  6. 在Servlet和HTML页面之间处理函数调用和数据传输的最佳方法是什
  7. 两个iframe之间实现锚点功能
  8. iframe操作、调用父页面元素或js函数
  9. 谁能帮忙注释一下这个js程序的功能?最好每一句都解释一下

随机推荐

  1. Android(安卓)Studio的基本控件 图片框与
  2. Android中自动朗读(TTS)的简单使用
  3. Android 插件化 动态升级
  4. Android(安卓)WebView的加载超时处理
  5. AndroidManifest.xml文件剖析
  6. android Aspectj实践问题
  7. android 一个很漂亮的控件ObservableScro
  8. Android性能调优总结
  9. 简析Android对Linux内核的改动
  10. Android开发秘籍学习笔记(八)