• 基于Springboot+redis+Springsecruity的网上商城 改改就可以做毕业设计


    前言

    最近复习了下SpringSecurity,发现看完之后还是和以前一样懵逼
    在这里插入图片描述
    既然理解不了,那我背下来可以把,就拿这玩意做一个小系统试试吧

    项目地址在最后的总结里,放在gitee里了,有兴趣的话可以来拿

    系统简介

    整个系统就只有一个主要业务,就是买东西,其他都是围绕着这个内容展开的。(该系统并没有使用支付宝等平台的支付接口,有兴趣的可以加一下)

    功能

    整个系统的功能如下图所示
    在这里插入图片描述

    使用技术

    模块技术
    web框架Springboot
    安全框架SpringSecurity
    数据库框架mybatis-plus
    前端semanticUI,html(建议大家使用Vue重写下)

    主要功能

    • 秒杀商品功能:这种场景下直接操作mysql数据库效率比较低,这里采用redis存储秒杀的商品,并使用乐观锁来实现商品货量的修改
    • 购买商品功能:相对于秒杀,并发量较少,采用直接操作mysql数据库的方式
    • 登录与授权:采用SpringSecurity作为安全框架,使用jwt方式存储账号信息,redis中存储着已登录用户的信息;添加一个自定义的过滤器,用于处理jwt中的账号信息,并进行拦截或放行以及授权。
    • 上/下架商品
    • 订单查看(用户/商家)
    • 退货申请/退货/处理退货申请
    • 评价(购买后)

    具体实现

    秒杀商品

    在这里插入图片描述

    我们先来分析下需求,首先就是一般秒杀都是在一个时间点然后才开始,开始后会同时涌入大量的用户进行购买。
    首先是第一个问题,怎么设置一个时间点开启秒杀,这里本来使用的是Timer开启一个定时任务,但本着能用框架就用框架的心,这里使用的是Quartz来实现定时任务的操作。
    这里简单描述下怎么使用Quartz,大致需要三个部分,任务,触发器和调度器,任务设置需要完成的事情,即操作redis中货品的信息,触发器设置开启时间,调度器根据触发器来调度任务。

    以下是使用的一个小实例

           LocalDateTime ctime = goods.getCtime();
            int year = ctime.getYear();
            int value = ctime.getMonth().getValue();
            int dayOfMonth = ctime.getDayOfMonth();
            int hour = ctime.getHour();
            int minute = ctime.getMinute();
    //        CronTrigger设置时间的语法   秒 分  时  天  月 ? 年
            String rtime = "0 " + minute + " " + hour + " " + dayOfMonth + " " + value + " ? " + year;
    //        设置JobDetail    这里设置自己设置的Job类信息
            JobDetail jobDetail = JobBuilder.newJob(MyJob.class)
                    .withIdentity("job" + LocalDateTime.now().toString(), "seckillgroup1")
                    //使用jobData传递信息到job中
                    .usingJobData("info", goods.getId().toString())
                    .usingJobData("size", goods.getSize().toString())
                    .build();
               //设置调度器
            CronTrigger trigger = (CronTrigger) TriggerBuilder.newTrigger()
                    .withIdentity("myTrigger" + LocalDateTime.now().toString(), "triggergroup1")
                    .withSchedule(
                    //设置任务开启时间
                            CronScheduleBuilder.cronSchedule(rtime)
                    ).build();
            try {
                Scheduler defaultScheduler = StdSchedulerFactory.getDefaultScheduler();
                //将任务和触发器添加进调度器中
                defaultScheduler.scheduleJob(jobDetail, trigger);
                defaultScheduler.start();
    
            } catch (SchedulerException e) {
                e.printStackTrace();
            }
    

    MyJob

    package com.xiaow.springsecuriydemo.vo;
    
    import com.xiaow.springsecuriydemo.utils.SpringContextUtil;
    import org.apache.catalina.core.ApplicationContext;
    import org.quartz.*;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.redis.core.RedisTemplate;
    
    import java.util.concurrent.TimeUnit;
    
    /**
     * 做定时器的任务 这里是做秒杀的业务
     *
     * @ClassName MyJob
     * @Author xiaow
     * @DATE 2022/9/18 19:07
     **/
    
    @DisallowConcurrentExecution   //设置之后表示不允许多线程进行
    @PersistJobDataAfterExecution   //设置之后就不会创造新的Myjob实例
    public class MyJob implements Job {
    
        /**
         * 秒杀商品的添加
         * @param jobExecutionContext
         * @throws JobExecutionException
         */
        @Override
        public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
            JobDetail jobDetail = jobExecutionContext.getJobDetail();
            JobDataMap jobDataMap = jobDetail.getJobDataMap();
            String info = (String) jobDataMap.get("info");
            String size = (String) jobDataMap.get("size");
            Integer num = Integer.parseInt(size);
            RedisTemplate redisTemplate = (RedisTemplate)
            SpringContextUtil.getApplicationContext().getBean("redisTemplate");
            redisTemplate.opsForValue().set("seckill" + info, num, 60 * 60 * 24, TimeUnit.SECONDS);
        }
    }
    
    

    接下来讲讲怎么购买,这个就相对简单些,也就是先判断redis中有无该商品的信息,如果有的话,开启乐观锁,然后进行商品数量减一操作,然后在操作成功后采取向数据库中添加购物信息

     /**
         * 秒杀商品购买
         *
         * @return
         */
        @PostMapping("/buySeckill")
        @PreAuthorize("hasAuthority('/user')")
    //    @Transactional
        public Result buySeckill(@RequestBody Goods goods) {
            System.out.println(goods);
    
            Goods byId = goodsService.getById(goods.getId());
            String key = "seckill" + goods.getId();
            Object o1 = redisTemplate.opsForValue().get("seckill" + goods.getId());
            if (Objects.isNull(o1))
                return Result.fail("还未开始抢购");
            //开启乐观锁
            redisTemplate.watch("seckill" + goods.getId());
            //开启多任务
            redisTemplate.multi();
            Integer remaining = (Integer) o1;
            if (remaining <= 0) {
                goodsService.updateById(byId.setState(3));
                return Result.fail("抢完了");
            }
            redisTemplate.opsForValue().set("seckill" + goods.getId(), remaining - 1);
            List exec = redisTemplate.exec();
            // exec==null代表操作失败,即需要在操作一次
            if (exec == null) {
                return Result.fail("请稍后重试,抢购失败");
            }
            //使用SpringSecurity中的对象获取用户信息
            Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
            LoginUser loginUser = (LoginUser) authentication.getPrincipal();
    
            byId.setRemaining(byId.getRemaining() - 1);
    //        更新数据库中的库存量
            boolean b = goodsService.updateById(byId);
    //        加入订单
            Trading trading = new Trading()
                    .setCtime(LocalDateTime.now())
                    .setGoodsid(goods.getId())
                    .setMoney(byId.getMoney())
                    .setStatus(0)
                    .setId(0)
                    .setUserid(loginUser.getUser().getId());
            boolean save = tradingService.save(trading);
            return Result.succ("抢购成功");
        }
    
    

    购买操作类似秒杀操作,只是不经过redis,直接操作数据库

    接入SpringSeCruity

    SecurityConfig

    package com.xiaow.springsecuriydemo.config;
    
    import com.xiaow.springsecuriydemo.exception.AuthenticationEntryPointImpl;
    import com.xiaow.springsecuriydemo.filter.JwtAuthenticationTokenFilter;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.authentication.AuthenticationManager;
    import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
    import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
    import org.springframework.security.config.http.SessionCreationPolicy;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
    import org.springframework.security.web.AuthenticationEntryPoint;
    import org.springframework.security.web.access.AccessDeniedHandler;
    import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
    
    /**
     * @ClassName Securityconfig
     * @Author xiaow
     * @DATE 2022/9/15 9:55
     **/
    @Configuration
    //开启注解授权认证的注解
    @EnableGlobalMethodSecurity(prePostEnabled = true)
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
    
        @Autowired
        AccessDeniedHandler accessDeniedHandler;
    
        @Autowired
        AuthenticationEntryPoint authenticationEntryPoint;
    
        @Autowired
        private JwtAuthenticationTokenFilter authenticationTokenFilter;
    
    
        //    重写密码加密器
        @Bean
        public BCryptPasswordEncoder passwordEncoder() {
            return new BCryptPasswordEncoder();
        }
    
    
        public static void main(String[] args) {
            String encode = new BCryptPasswordEncoder().encode("123");
            System.out.println(encode);
        }
    
        @Bean
        @Override
        public AuthenticationManager authenticationManagerBean() throws Exception {
            return super.authenticationManagerBean();
        }
    
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
    //               解决跨域问题
                    .cors().and()
    //                关闭csrf
                    .csrf().disable()
    //                禁用session
                    .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                    .and()
                    .authorizeRequests()
    //                放行接口 不需要认证
                    .antMatchers("/login", "/shoper/login", "/user/state", "/goods/getAllGoods"
                            , "/goods/getGoodsById", "/goods/getAllSeckillGoods", "/goods/getSeckillById", "/comments/getCommentsByGoodsId", "/goods/getAllByShopIdAll"
                            , "/shop/getShopInfoByShopId"
                    ).permitAll()
                    .anyRequest().authenticated();
    
    //    把我们自己写好的过滤器添加到过滤器的前面   这个很重要
            http.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
    
    
    //        配置异常处理器
    //        分别是认证异常处理和授权处理
            http.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint)
                    .accessDeniedHandler(accessDeniedHandler);
        }
    }
    
    

    设置自定义过滤器

    package com.xiaow.springsecuriydemo.filter;
    
    import com.xiaow.springsecuriydemo.entity.Roleandperm;
    import com.xiaow.springsecuriydemo.entity.User;
    import com.xiaow.springsecuriydemo.service.RoleandpermService;
    import com.xiaow.springsecuriydemo.utils.JwtUtils;
    import com.xiaow.springsecuriydemo.vo.LoginUser;
    import io.jsonwebtoken.Claims;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
    import org.springframework.security.core.GrantedAuthority;
    import org.springframework.security.core.authority.SimpleGrantedAuthority;
    import org.springframework.security.core.context.SecurityContextHolder;
    import org.springframework.stereotype.Component;
    import org.springframework.util.StringUtils;
    import org.springframework.web.filter.OncePerRequestFilter;
    
    import javax.servlet.FilterChain;
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    import java.util.LinkedList;
    import java.util.List;
    import java.util.Objects;
    
    /**
     * @ClassName JwtAuthenticationTokenFilter
     * @Author xiaow
     * @DATE 2022/9/15 11:03
     **/
    @Component
    public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
        @Autowired
        private RedisTemplate redisTemplate;
    
        @Autowired
        RoleandpermService roleandpermService;
        @Override
        protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
    //获取token  这里的token是由前端发起的请求中的header中存储的
            String token = httpServletRequest.getHeader("token");
    //        System.out.println(token);
            if (!StringUtils.hasText(token)) {
    //            这里放行就是让其他的过滤器帮我们解决未登录
                filterChain.doFilter(httpServletRequest, httpServletResponse);
    //            return是必须的,防止继续进行下面代码
                return;
            }
    //        解析token
            String userid = "";
    
            try {
                Claims claims = JwtUtils.getClaims(token);
                userid = (String) claims.get("userid");
            } catch (Exception e) {
                e.printStackTrace();
                throw new RuntimeException("解析token异常");
            }
    
    //        redis中获取信息
            User o = (User) redisTemplate.opsForValue().get("login" + userid);
            if (Objects.isNull(o)) {
                throw new RuntimeException("token异常");
            }
            List<Roleandperm> byUserId = roleandpermService.getByUserId(o.getId());
            List<GrantedAuthority> newList=new LinkedList<>();
            List<String> perms=new LinkedList<>();
            byUserId.forEach(p->{
                SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(p.getPerm());
                newList.add(simpleGrantedAuthority);
                perms.add(p.getPerm());
            });
    //        存入SecurityContextgholder,因为后续的过滤器需要在这个东西中找到认证的信息   这个很重要
            UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(new LoginUser(o,perms), null, newList);
            SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
    
            filterChain.doFilter(httpServletRequest, httpServletResponse);
        }
    }
    
    

    登录异常和授权异常处理

    处理权限不足时的异常处理器
    设置这里,就是当权限不足时,可以以我们喜欢的方式提醒我们

    package com.xiaow.springsecuriydemo.exception;
    
    import com.alibaba.fastjson.JSON;
    import com.xiaow.springsecuriydemo.utils.WebUtils;
    import com.xiaow.springsecuriydemo.vo.Result;
    import org.springframework.security.access.AccessDeniedException;
    import org.springframework.security.web.access.AccessDeniedHandler;
    import org.springframework.stereotype.Component;
    
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    
    /**
     * @ClassName AccessDeniedHandlerImpl
     * @Author xiaow
     * @DATE 2022/9/16 19:28
     **/
    @Component
    //处理权限不足时的异常处理器
    public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
        @Override
        public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
    //处理异常
            Result result = Result.result("您的权限不足", 403, "");
            String s = JSON.toJSONString(result);
            WebUtils.renderString(httpServletResponse,s);
        }
    }
    
    

    **登陆异常处理器 **

    package com.xiaow.springsecuriydemo.exception;
    
    import com.alibaba.fastjson.JSON;
    import com.xiaow.springsecuriydemo.utils.WebUtils;
    import com.xiaow.springsecuriydemo.vo.Result;
    import org.springframework.security.core.AuthenticationException;
    import org.springframework.security.web.AuthenticationEntryPoint;
    import org.springframework.stereotype.Component;
    
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    
    /**
     * @ClassName AuthenticationEntryPointImpl
     * @Author xiaow
     * @DATE 2022/9/16 19:22
     **/
    //认证异常的处理
        @Component
    public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
        @Override
        public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
    //处理异常
            Result result = Result.result("用户名认证失败请重新登录", 401, "");
            String s = JSON.toJSONString(result);
            WebUtils.renderString(httpServletResponse,s);
        }
    }
    
    

    这里还有很多配置没有提到,有兴趣的话可以去看看项目源码,都有

    其他功能

    放几张系统的图片
    在这里插入图片描述
    在这里插入图片描述

    相对于秒杀来说,其他的业务都相对简单,也就是对数据库的增删查改,这里就不过多描述了。

    这里的文件上传功能使用一个单独的服务来实现,有需要的可以来这里找一下文件服务代码,直接用

    文件上传主要用于在商家上架商品和秒杀时使用

    总结

    有点标题党了,如果说毕设要求比较高的话,可以再加点东西。这里提几个改进的地方

    • Quartz可以设置成持久化的,这个项目中使用的还差点意思
    • 可以搞一个Redis集群
    • 系统功能可以在划分一下,分成多个微服务

    其他的大家就自己想了

    项目地址项目源码

  • 相关阅读:
    【Python程序设计】基于Flask的防疫宣传网站
    Vim基本配置&快捷键&常用命令
    归并排序及其时间复杂度分析
    2022unity超简单课设-模拟太阳系的Unity小游戏
    Linux之慢盘检测
    什么是“孤岛效应”? ----防孤岛保护装置
    STM32CubeIDE实现基于STM32的LoRa通信程序移植(SPI接口)
    Simulink脚本自动创建Autosar Parameter Port及Mapping
    Mysql整理-SQL语言
    阿里云日志上报乱码问题记录
  • 原文地址:https://blog.csdn.net/qq_43627076/article/details/126946295