chenyeshen / tmall

使用springboot模仿天猫网站

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

tmall_springboot

简介

使用springboot模仿天猫网站
采用spring,hibernate,springboot框架。数据层采用JPA。
数据库采用mysql。
redis作为缓存服务器。
前端采用html, CSS, Javascript, JSON, AJAX, JQuery ,Bootstrap, Vue等。
restful风格设计,采用axios实现前后端数据传输。
采用shiro实现登陆功能.
elasticsearch实现搜索功能,可实现重复条件搜索无需访问数据库。
前台为包括用户登陆、首页、商品详情页和支付页面等。后台分类展示商品,添加和修改商品,包括上传商品图片。添加和修改商品属性,查看用户列表等。

前端使用vue生成对象渲染模板,浏览器接受渲染后的页面,使用axios接受浏览器发送的信息,向服务器发送和接收响应。(Axios 是一个基于 promise 的 HTTP 库,可以自动将信息转换为json格式。)

后端数据层采用hibernate框架加jpa。

Result类用来向前端返回json对象信息,实现了restful的设计风格,使代码更加整齐。

public class Result {

    private static int SUCCESS_CODE = 0;
    private static int FAIL_CODE = 1;

    private int code;
    private String message;
    private Object data;

    private Result(int code, String message, Object data) {
        this.code = code;
        this.message = message;
        this.data = data;
    }

    public static Result success() {
        return new Result(SUCCESS_CODE, null, null);
    }

    public static Result success(Object data) {
        return new Result(SUCCESS_CODE, "", data);
    }

    public static Result fail(String message) {
        return new Result(FAIL_CODE, message, null);
    }

本网站的登陆功能包括前台用户和后台管理员登陆。用户和管理员通过不同的页面和登陆验证策略。

管理员realm(这里我没有加入登陆功能,而是在数据库中直接加入管理员账号后再登陆时生成salt,加密密码后覆盖原来的数据库中密码)

public class AdminRealm extends AuthorizingRealm {

    @Autowired
    AdministratorService administratorService;

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        return simpleAuthorizationInfo;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        CustomizedToken customizedToken = (CustomizedToken) authenticationToken;
        String adminName = customizedToken.getPrincipal().toString();
        Administrator admin = administratorService.getByName(adminName);
        if(admin == null){
            throw new UnknownAccountException("账号不存在");
        }
        String password = admin.getPassword();
        String salt = admin.getSalt();
        String realmName = getName();
        if (salt == null) {
            salt = new SecureRandomNumberGenerator().nextBytes().toHex();
            password = new SimpleHash("md5", password, salt, 2).toString();
            admin.setPassword(password);
            admin.setSalt(salt);
            administratorService.update(admin);
        }
            SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(adminName, password, ByteSource.Util.bytes(salt), realmName);
            return authenticationInfo;
    }
}

实现用户登录的realm

public class UserRealm extends AuthorizingRealm {
    @Autowired
    UserService userService;

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        SimpleAuthorizationInfo s = new SimpleAuthorizationInfo();
        return s;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        CustomizedToken customizedToken = (CustomizedToken) authenticationToken;
        String userName = customizedToken.getPrincipal().toString();
        User user = userService.getByName(userName);
        String passwordInDB = user.getPassword();
        String salt = user.getSalt();
        String realmName = getName();
        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(userName, passwordInDB, ByteSource.Util.bytes(salt), realmName);
        return authenticationInfo;
    }

继承ModularRealmAuthenticator,实现根据登陆令牌不同选择不同的realm。

public class CustomizedModularRealmAuthenticator extends ModularRealmAuthenticator {
    @Override
    protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
        // 判断getRealms()是否返回为空
        assertRealmsConfigured();
        // 强制转换回自定义的CustomizedToken
        CustomizedToken customizedToken = (CustomizedToken) authenticationToken;
        // 登录类型
        String loginType = customizedToken.getLoginType();
        // 所有Realm
        Collection<Realm> realms = getRealms();
        // 登录类型对应的所有Realm
        Collection<Realm> typeRealms = new ArrayList<>();
        for(Realm realm:realms) {
            if (realm.getName().contains(loginType)) {
                typeRealms.add(realm);
            }
        }
            if(typeRealms.size()==1){
                return doSingleRealmAuthentication(typeRealms.iterator().next(),customizedToken);
            }else{
                return doMultiRealmAuthentication(typeRealms,customizedToken);
            }
    }
}

继承UsernamePasswordToken类,实现自己的令牌类

public class CustomizedToken extends UsernamePasswordToken {

    private String loginType;

    public CustomizedToken(final String name,final String password,String loginType){
        super(name,password);
        this.loginType =loginType;
    }

    public String getLoginType() {
        return loginType;
    }

    public void setLoginType(String loginType) {
        this.loginType = loginType;
    }
}

为不同登陆类型赋值的枚举类

public enum LoginType {
    /**
     * 用户类型登录
     */
    USER("User"),
    /**
     * 管理员类型登陆
     */
    ADMIN("Admin");

    private String type;

    private LoginType(String type){
        this.type = type;
    }

    @Override
    public String toString() {
        return type;
    }
}

在shiro配置类中配置多realm

@Bean
    public SecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setAuthenticator(modularRealmAuthenticator());
        List<Realm> realms = new ArrayList<>();
        realms.add(getUserRealm());
        realms.add(getAdminRealm());
        securityManager.setRealms(realms);
        return securityManager;
    }

我选择的策略为FirstSuccessfulStrategy,只有一个realm生效即可。

    @Bean
    public CustomizedModularRealmAuthenticator modularRealmAuthenticator(){
        CustomizedModularRealmAuthenticator modularRealmAuthenticator = new CustomizedModularRealmAuthenticator();
        modularRealmAuthenticator.setAuthenticationStrategy(new FirstSuccessfulStrategy());
        return modularRealmAuthenticator;
    }

在Controller中就可以根据登陆类型不同进行处理,以AdminController为例:

    private static final String USER_LOGIN_TYPE = LoginType.USER.toString();
    
    @PostMapping("/forelogin")
    public Object login(@RequestBody User userParam, HttpSession session) {
        String name = userParam.getName();
        name = HtmlUtils.htmlEscape(name);
        String password = userParam.getPassword();
        Subject currentUser = SecurityUtils.getSubject();

        if (!currentUser.isAuthenticated()) {
            UsernamePasswordToken token = new CustomizedToken(name, password, USER_LOGIN_TYPE);
            try {
                currentUser.login(token);
                User user = userService.getByName(name);
                session.setAttribute("user", user);
                return Result.success("登陆成功");
            } catch (IncorrectCredentialsException e) {
                return Result.fail("账号密码错误");
            }catch (LockedAccountException e){
                return Result.fail("账号已被锁定");
            }
        }else {
            return Result.success("已登陆");
        }
    }

最后还要根据不同的请求路径配置路径拦截类,在WebMvcConfigurer中配置这些拦截器。

前台购物车功能也是基于Interceptor实现的:

public class OtherInterceptor implements HandlerInterceptor {
    @Autowired
    CategoryService categoryService;
    @Autowired
    OrderItemService orderItemService;

    @Override
    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
        HttpSession session = httpServletRequest.getSession();
        User user = (User) session.getAttribute("user");
        int cartTotalItemNumber = 0;
        if (user != null) {
            List<OrderItem> ois = orderItemService.listByUser(user);
            for (OrderItem orderItem : ois) {
                cartTotalItemNumber += orderItem.getNumber();
            }
        }
        List<Category> cs = categoryService.list();
        String contextPath = httpServletRequest.getServletContext().getContextPath();
        //把分类list存放在servlet上下文中
        httpServletRequest.getServletContext().setAttribute("categories_below_search", cs);
        session.setAttribute("cartTotalNumber", cartTotalItemNumber);   //购物车数量保存在session中
        httpServletRequest.getServletContext().setAttribute("contextPath", contextPath);
    }

    @Override
    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {

    }
}

在service层的修改数据库方法上加入事务注解,当出现异常时可进行回滚.Propagation.REQUIRED是事务的传播行为:如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。 @CacheEvict注解是在该调用方法时删除redis缓存中的所有数据,避免下次服务器读取到过时的数据。@Cacheable则是向缓存中保存查询到的数据,这样就不用重复器数据库中读取。

    @CacheEvict(allEntries = true)
    @Transactional(propagation = Propagation.REQUIRED, rollbackForClassName = "Exception")
    public float add(Order order, List<OrderItem> ois) {  //计算订单总价
        float total = 0;
        add(order);
        if (false) {
            throw new RuntimeException();
        } else {
            for (OrderItem oi : ois) {
                oi.setOrder(order);
                orderItemService.update(oi);
                total += oi.getProduct().getPromotePrice() * oi.getNumber();
            }
        }
        return total;
    }

前台商品排序功能的实现: 首先为商品类实现一个比较器

public class ProductSaleCountComparator implements Comparator<Product> {
    @Override
    public int compare(Product o1, Product o2) {
        return o2.getSaleCount() - o1.getSaleCount();
    }
}

controller的代码如下:

@GetMapping("forecategory/{cid}")
    public Object category(@PathVariable int cid, String sort) {
        Category category = categoryService.get(cid);
        productService.fill(category);
        productService.setSaleAndReviewNumber(category.getProducts());
        categoryService.removeCategoryFromProduct(category);
        if (sort != null) {
            switch (sort) {
                case "review":
                    Collections.sort(category.getProducts(), new ProductReviewComparator());
                    break;
                case "date":
                    Collections.sort(category.getProducts(), new ProductDateComparator());
                    break;
                case "saleCount":
                    Collections.sort(category.getProducts(), new ProductSaleCountComparator());
                    break;
                case "price":
                    Collections.sort(category.getProducts(), new ProductPriceComparator());
                    break;
                case "all":
                    Collections.sort(category.getProducts(), new ProductAllComparator());
                    break;
                    default:
            }
        }
        return category;
    }

About

使用springboot模仿天猫网站


Languages

Language:HTML 33.7%Language:JavaScript 31.3%Language:Java 26.3%Language:CSS 8.7%