前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >基于Springboot+redis+Springsecruity的网上商城 改改就可以做毕业设计

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

作者头像
小王不头秃
发布2024-06-19 17:14:11
1220
发布2024-06-19 17:14:11
举报

前言

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

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

系统简介

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

功能

整个系统的功能如下图所示

使用技术

模块

技术

web框架

Springboot

安全框架

SpringSecurity

数据库框架

mybatis-plus

前端

semanticUI,html(建议大家使用Vue重写下)

主要功能

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

具体实现

秒杀商品

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

以下是使用的一个小实例

代码语言:javascript
复制
       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

代码语言:javascript
复制
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中有无该商品的信息,如果有的话,开启乐观锁,然后进行商品数量减一操作,然后在操作成功后采取向数据库中添加购物信息

代码语言:javascript
复制
 /**
     * 秒杀商品购买
     *
     * @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
代码语言:javascript
复制
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);
    }
}
设置自定义过滤器
代码语言:javascript
复制
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);
    }
}
登录异常和授权异常处理

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

代码语言:javascript
复制
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);
    }
}

**登陆异常处理器 **

代码语言:javascript
复制
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集群
  • 系统功能可以在划分一下,分成多个微服务

其他的大家就自己想了

项目地址项目源码

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-06-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 系统简介
    • 功能
      • 使用技术
        • 主要功能
        • 具体实现
          • 秒杀商品
            • 接入SpringSeCruity
              • SecurityConfig
              • 设置自定义过滤器
              • 登录异常和授权异常处理
            • 其他功能
            • 总结
            相关产品与服务
            云数据库 Redis
            腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档