1. 程式人生 > >電商平臺的搭建(SpringMVC+SpringSecurity/Validation+Redis+MySQL+React)----登陸註冊模組

電商平臺的搭建(SpringMVC+SpringSecurity/Validation+Redis+MySQL+React)----登陸註冊模組

工作之餘搭建的電商平臺,現已存在的功能有:
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 ,這塊還是分開寫吧,下一次寫一下購物車功能的實現,做過的東西記性不好的話 要拿來經常總結的,下次假如用到了,可以用這個臨時大腦幫忙回憶一下。