工作之余搭建的电商平台,现已存在的功能有:
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 ,这块还是分开写吧,下一次写一下购物车功能的实现,做过的东西记性不好的话 要拿来经常总结的,下次假如用到了,可以用这个临时大脑帮忙回忆一下。