电商平台的搭建(SpringMVC+SpringSecurity/Validation+Redis+MySQL+React)----登陆注册模块

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/linfujian1999/article/details/74537600

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

猜你喜欢

转载自blog.csdn.net/linfujian1999/article/details/74537600
今日推荐