基于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集群
  • 系统功能可以在划分一下,分成多个微服务

其他的大家就自己想了

项目地址项目源码

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小王不头秃

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值