基于spring boot实现的Probe社区项目

基于spring boot实现的Probe社区项目 1,项目简介 probe社区项目是一套用于手机端的动态网站,后端基于SpringBoot+MyBatisPlus实现

本文包含相关资料包-----> 点击直达获取<-------

基于spring boot实现的Probe社区项目

1.项目简介

probe社区项目是一套用于手机端的动态网站,后端基于SpringBoot+MyBatisPlus实现,前端页面使用html+css+js实现包含动态搜索、动态内容展示、个人中心、反馈中心等模块,Probe社区意在成为一款给广大学生来进行相互分享动态的BBS社区平台

1.1 使用的技术

probe采用现阶主流技术实现,涵盖了一般项目中几乎所有使用的技术。

技术 版本 说明
Spring Boot 2.1.3 容器+MVC框架
Apache Shiro 1.4.1 Java安全框架,执行身份验证和授权
MyBatisPlus 3.4.6 ORM框架,MyBatis的增强工具
Elasticsearch 7.6.1 搜索引擎
Redis 2.9.0 分布式缓存
Swagger-UI 2.7.0 文档生产工具
Druid 1.1.10 数据库连接池
Lombok 1.18.6 简化对象封装工具
OSS 2.5.0 存储图片
Jwt 3.2.0 与Shiro框架结合实现验证和授权

1.2 实现的功能概览

  • 用户模块
  • 个人信息编辑设置
  • 用户注册和修改密码
  • 手机号和邮箱绑定设置
  • 用户反馈内容管理
  • 发布动态内容和添加水印
  • 对动态内容进行点赞、收藏和评论回复
  • 记录用户浏览动态的足迹

  • 动态模块

  • 按不同种类分页显示相关动态
  • 分页显示相关评论
  • 动态图片查看

  • 后台管理模块

  • 使用折线图和饼图来显示统计相关数据
  • 用户、话题、留言以及管理员列表的相关CRUD操作
  • 回收站列表,即在话题列表中删除话题会在回收站中显示,并设置计时器在一个月后自动删除
  • 管理员列表中可以修改相关管理员的角色权限

2.数据库设计

2.1 表结构

视频表

帖子表

反馈表

帖子图片表

用户表

访问记录表

分类表

评论表

2.2 E-R图

3.项目设计

3.1 常用工具类

JwtToken生成的工具类

因为要整合了 JWT ,我们需要自定义过滤器 JWTFilter。JWTFilter 继承了 BasicHttpAuthenticationFilter,并部分原方法进行了重写。

该过滤器主要有三步:

  • 检验请求头是否带有 Token: ((HttpServletRequest) request).getHeader(“Token”)

  • 如果带有 Token ,则执行 Shiro 中的 login() 方法,该方法将导致

  • 将 Token 提交到 Realm 中进行验证(执行自定义的Reaml中的方法)
  • 如果没有 Token,则说明当前状态为游客状态或者其他一些不需要进行认证的接口

  • 如果在 Token 校验的过程中出现错误,如:Token 校验失败,那么我会将该请求视为认证不通过,则重定向到 /unauthorized/**

```java /* * JwtToken生成的工具类 * JWT token的格式:header.payload.signature * header的格式(算法、token的类型): * {"alg": "HS512","typ": "JWT"} * payload的格式(用户名、创建时间、生成时间): * {"sub":"wang","created":1489079981393,"exp":1489684781} * signature的生成算法: * HMACSHA512(base64UrlEncode(header) + "." +base64UrlEncode(payload),secret) * * @author sakura * @date 2018/4/26 / @Slf4j public class JwtTokenUtil { // 过期时间 1小时 private static final Long EXPIRE_TIME = 60 * 60 * 1000L; // 密匙 private static final String SECRET = "SHIRO+JWT";

/**
 * 生成 token 时,指定 token 过期时间 EXPIRE_TIME 和签名密钥 SECRET,
 * 然后将 expireDate 和 username 写入 token 中,并使用带有密钥的 HS256 签名算法进行签名
 * @param username
 * @return
 */
public static String createToken(String username){
    String token = null;
    try {
        // 设置token的过期时间
        Date expireDate = new Date(System.currentTimeMillis()+EXPIRE_TIME);
        // 加密算法
        Algorithm algorithm = Algorithm.HMAC256(SECRET);
        token = JWT.create()
                .withClaim("username", username)
                .withExpiresAt(expireDate)
                .sign(algorithm);
    } catch (UnsupportedEncodingException e) {
        log.error("Failed to create token.{}",e.getMessage());
    }
    return token;
}

/**
 * 验证token,如果验证失败,便会抛出异常
 * @param token
 * @param username
 * @return
 */
public static boolean verify(String token,String username){
    boolean isSuccess = false;
    try {
        Algorithm algorithm = Algorithm.HMAC256(SECRET);
        JWTVerifier verifier = JWT.require(algorithm)
                .withClaim("username", username)
                .build();
        // 验证token
        verifier.verify(token);
        isSuccess = true;
    } catch (UnsupportedEncodingException e) {
        log.error("Token is invalid. {}", e.getMessage());
    } catch (TokenExpiredException e){
        log.error("Token is out of data");
    }
    return isSuccess;
}

/**
 * 在 createToken()方法里,有将 username 写入 token 中。现在可从 token 里获取 username
 * @param token
 * @return
 */
public static String getUsernameFromToken(String token) {
    try {
        DecodedJWT decode = JWT.decode(token);
        String username = decode.getClaim("username").asString();
        return username;
    } catch (JWTDecodeException e) {
        log.error("Failed to Decode jwt. {}", e.getMessage());
        return null;
    }
}

```

发邮件的工具类

```java public class MailUtil { private static final String USER = " * @qq.com"; // 本人的发件人称号,同邮箱地址 private static final String PASSWORD = " * *"; // 如果是qq邮箱可以使户端授权码,或者登录密码

/**
 *
 * @param to 收件人邮箱
 * @param text 邮件正文
 * @param title 标题
 */
/* 发送验证信息的邮件 */
public static boolean sendMail(String to, String text, String title){
    try {
        final Properties props = new Properties();
        props.put("mail.smtp.auth", "true");
        props.put("mail.smtp.host", "smtp.qq.com");

        // 发件人的账号
        props.put("mail.user", USER);
        //发件人的密码
        props.put("mail.password", PASSWORD);

        // 构建授权信息,用于进行SMTP进行身份验证
        Authenticator authenticator = new Authenticator() {
            @Override
            protected PasswordAuthentication getPasswordAuthentication() {
                // 用户名、密码
                String userName = props.getProperty("mail.user");
                String password = props.getProperty("mail.password");
                return new PasswordAuthentication(userName, password);
            }
        };
        // 使用环境属性和授权信息,创建邮件会话
        Session mailSession = Session.getInstance(props, authenticator);
        // 创建邮件消息
        MimeMessage message = new MimeMessage(mailSession);
        // 设置发件人
        String username = props.getProperty("mail.user");
        InternetAddress form = new InternetAddress(username);
        message.setFrom(form);

        // 设置收件人
        InternetAddress toAddress = new InternetAddress(to);
        message.setRecipient(Message.RecipientType.TO, toAddress);

        // 设置邮件标题
        message.setSubject(title);

        // 设置邮件的内容体
        message.setContent(text, "text/html;charset=UTF-8");
        // 发送邮件
        Transport.send(message);
        return true;
    }catch (Exception e){
        e.printStackTrace();
    }
    return false;
}

/**
 * 发送验证码到指定邮箱并且返回生成的验证码
 * @param mail
 * @return
 */
public static String sendMail(String mail){
    //发送的验证码
    String code = RandomUtil.randomNumbers(4);
    String content = "【社团probe】您正在使用邮箱验证,验证码为"+
            code+",此验证码仅用于修改密码验证。请勿泄露给他人,5分钟内有效!";
    String title = "[社团probe]验证码";
    MailUtil.sendMail(mail,content,title);
    return code;
}

```

阿里云手机短信服务

```java public class SMSUtil {

/**
 * 发送验证码到指定手机上
 * @param phonenumbers 手机号
 * @param code 验证码
 * @return
 */
public static boolean sendcode(String phonenumbers,String code){
    //连接阿里云
    DefaultProfile profile = DefaultProfile.getProfile("cn-hangzhou", "accessKeyId", "secret");
    IAcsClient client = new DefaultAcsClient(profile);

    CommonRequest request = new CommonRequest();
    request.setMethod(MethodType.POST);
    request.setDomain("dysmsapi.aliyuncs.com");//不要动
    request.setVersion("2017-05-25");//
    request.setAction("SendSms");

    //自定义的参数 (手机号、验证码、签名、模板)
    request.putQueryParameter("RegionId", "cn-hangzhou");
    request.putQueryParameter("PhoneNumbers", phonenumbers);
    request.putQueryParameter("SignName", "社团probe");
    request.putQueryParameter("TemplateCode", "SMS_189616534");
    HashMap<String,Object> map = new HashMap<>();
    map.put("code",code);
    request.putQueryParameter("TemplateParam", JSON.toJSONString(map));
    try {
        CommonResponse response = client.getCommonResponse(request);
        System.out.println(response.getData());
        return response.getHttpResponse().isSuccess();
    } catch (ServerException e) {
        e.printStackTrace();
    } catch (ClientException e) {
        e.printStackTrace();
    }
    return false;
}

public static void main(String[] args) {
    String s = RandomUtil.randomNumbers(4);
    System.out.println(s);
    //sendcode("15521003562","2233");
}

} ```

3.2 捕获异常

```java /* 捕获与Shiro相关的异常 / @ExceptionHandler(ShiroException.class) public CommonResult handle401(){ //您没有权限访问! return CommonResult.forbidden(null); }

/**捕获其他异常*/
@ExceptionHandler(Exception.class)
public CommonResult globalException(HttpServletRequest request, Throwable e){
    return CommonResult.failed(getStatus(request).value(),
            "访问出错,无法访问:"+e.getMessage());
}

private HttpStatus getStatus(HttpServletRequest request) {
    Integer statusCode = (Integer) request.getAttribute("java.servlet.error.status_code");
    if (null == statusCode){
        return HttpStatus.INTERNAL_SERVER_ERROR;
    }
    return HttpStatus.valueOf(statusCode);
}

} ```

3.3 业务代码

```java //视频最大上传量 final long MAX_VIDEO_SIZE = 1024 1024 500;

//支持上传图片的最大数量 private static final int IMAGEMAXCOUNT = 6;

@ApiOperation("获取话题类型") @ResponseBody @GetMapping(value = "/getTopicCategory") public CommonResult > getTopicCategory(){ List list = topicCategoryService.getTopicCategoryList(); return list==null?CommonResult.failed():CommonResult.success(list); }

@ApiOperation("获取发布内容的信息以及图片,并存入数据库中") @ResponseBody @PostMapping(value = "/publishTopic") @RequiresRoles(logical = Logical.OR,value = {"0","1","2","3"}) public CommonResult publishTopic(HttpServletRequest request,int type, String topicStr,String token,Integer head){ List fileList = new ArrayList<>(); Topic topic = null; MultipartFile video; CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver(request.getSession().getServletContext()); //接收前端参数的变量的初始化 ObjectMapper mapper = new ObjectMapper(); MultipartHttpServletRequest multiRequest = (MultipartHttpServletRequest) request; if (type==1){ //上传图片 //若请求中存在文件流,则取出相关的文件 try { if(multipartResolver.isMultipart(request)){ getTopicImgList(multiRequest,fileList); } } catch (Exception e) { return CommonResult.failed(e.getMessage()); } }else { try { //上传视频 if (multipartResolver.isMultipart(request)){ video = multiRequest.getFile("video"); if (video.getSize()>MAX_VIDEO_SIZE){ return CommonResult.failed("上传的视频最大不能超过500M"); }else if (video.getOriginalFilename().indexOf(".mp4")<0){ return CommonResult.failed("视频上传格式只支持mp4"); } fileList.add(video); } } catch (Exception e) { return CommonResult.failed(e.getMessage()); } } try { //尝试获取前端传过来的表单string流并将其转换成Topic实体类 topic = mapper.readValue(topicStr, Topic.class); } catch (IOException e) { return CommonResult.failed(e.getMessage()); } if (topic!=null&&!topic.getTopicName().equals("")){ //从redis中获取用户信息 PersonInfo person = redisService.getPersonByToken(token); //将正文的换行符转换成
topic.setOwnerId(person.getUserId()).setPersonInfo(person) .setTopicDesc(StringEncoder.inDatabase(topic.getTopicDesc())); if (null!=head){ topic.setEnableHeadline(head); } log.info("topic="+topic.toString()); //调用service方法进行添加 return topicService.save(topic,fileList,type); }else { return CommonResult.failed("信息未填写完整"); } }

@ApiOperation("返回话题列表页中对应的列表信息") @ResponseBody @GetMapping(value = "/topicListInfo") public Map TopicListInfo(int pageIndex,int pageSize,boolean type, Long topicCategoryId,String topicName){ HashMap modelMap = new HashMap<>(); Topic condition = new Topic(topicCategoryId,topicName); condition.setDeleted(0); CommonResult > res; if (type){ res = esSearchService.getTopicList(condition, pageIndex, pageSize); }else { res = topicService.listByCondition(condition, pageIndex, pageSize); } modelMap.put("result",res); modelMap.put("count",topicService.countByCondition(condition)); modelMap.put("success",true); return modelMap; }

@ApiOperation("根据话题id获取帖子正文内容") @ResponseBody @GetMapping("/getTopicInfo") public CommonResult TopicInfo(Long topicId,String token){ return topicService.getTopicInfo(topicId,JwtTokenUtil.getUsernameFromToken(token)); }

/* * 由于点赞和收藏存在高并发的问题,因此先将对应的信息(即是点赞还是取消点赞)加入到redis中做为缓存 * 再设置一个定时器,每间隔一段时间就从redis中取出对应的数据 * 简单地来说就是redis 异步入库,就是点赞和取消都交给 redis,redis 记录了点赞人和被点赞人, * 同时在另外记录点赞总数,然后通过定时任务进行异步落库并删除 redis 中的指定数据。 * * 关于在不使用mysql存储用户点赞和收藏操作时,如何标识用户是否对该话题进行点赞和收藏的 * 这里我统一使用redis进行存储用户对话题的点赞和收藏信息 * key使用PersonThumb_userId , value使用set集合来存储用户点赞话题的id * @param topicId 需要操作的话题id * @param type true-点赞 false-收藏 / @ApiOperation("为帖子(添加/取消)点赞或收藏") @ResponseBody @GetMapping("/thumbOrCollect") @RequiresRoles(logical = Logical.OR,value = {"0","1","2","3"}) public synchronized CommonResult thumbOrCollect(String topicId,boolean type, HttpServletRequest request){ String userId = JwtTokenUtil.getUsernameFromToken(request.getHeader("Token")); try { String topicKey = type?RedisKey.getThumbTopicIdList():RedisKey.getCollectTopicIdList(); String personKey = type?RedisKey.getPersonThumb():RedisKey.getPersonCollect(); //获取该用户点赞(收藏)的列表 Set personSet = redisService.getSet(personKey + userId); if (null==personSet||!personSet.contains(topicId)){ //说明该用户没有对该话题进行点赞(收藏) if (null==personSet){ personSet = new HashSet<>(); } //在对应列表中添加上该话题id personSet.add(topicId); //并且将该帖子的点赞(收藏)量加一 redisService.increment(topicKey+topicId,1); }else if (personSet.contains(topicId)){ //说明该用户之前已经对该话题进行了点赞(收藏) personSet.remove(topicId); //并且将该帖子的信息点赞(收藏)量减一 redisService.decrement(topicKey+topicId,1); } //重新添加到redis中 redisService.setSet(personKey + userId,personSet); } catch (Exception e) { return CommonResult.failed(e.getMessage()); } return CommonResult.success(null); }

/* * 当用户浏览当前话题时先从redis中取出当前用户的浏览记录 * 如果当前话题为第一次访问则将信息缓存到redis中并且将相关信息存储在mysql中 * redis在其中起到判断用户是否第一次浏览该话题 / @ApiOperation("每当浏览当前话题时更新当前用户的浏览记录") @ResponseBody @GetMapping("/addFootPrint") @RequiresRoles(logical = Logical.OR,value = {"0","1","2","3"}) public CommonResult addFootPrint(HttpServletRequest request,Long topicId){ String token = request.getHeader("Token"); Long userId = Long.valueOf(JwtTokenUtil.getUsernameFromToken(token)); FootPrint footPrint = new FootPrint(userId,topicId); //先判断该用户是否是第一次浏览该话题 String key = RedisKey.getPerson_FootPrint()+userId; Set personSet = redisService.getSet(key); if (null==personSet){ //表示该用户是第一次访问该话题 personSet = new HashSet<>(); footPrintService.save(footPrint); personSet.add(userId+""); }else { //表示该用户不是第一次访问该话题 footPrintService.updateById(footPrint); } redisService.setSet(key,personSet); return CommonResult.success(null); }

private void getTopicImgList(MultipartHttpServletRequest request, List topicImgList) { MultipartHttpServletRequest multiRequest = request; //取出图片列表并构建List 列表对象,最多支持六张图片上传 for(int i=0;i<IMAGEMAXCOUNT;i++){ CommonsMultipartFile topicImgFile = (CommonsMultipartFile) multiRequest.getFile("topicImg"+i); if (topicImgFile!=null){ System.out.println("图片的大小:"+topicImgFile.getSize()); //若取出的第i个详情图片文件流不为空,则将其加入list集合中 topicImgList.add(topicImgFile); }else { break; } ```

4.项目展示

4.1 前端

注册页面

登录后进入的页面

点击右边的侧边栏可以发布动态或进入个人中心

话题专区

搜索相关动态时使用elasticsearch进行查找,并将关键字进行高亮

动态内容

点击动态图片对图片进行浏览

个人中心

对信息进行编辑

在账号安全中心可以对个人中手机号和邮箱进行绑定

4.2 后台管理界面

登录界面

统计页面

用户列表界面

用户列表界面进行批量删除

轮播图列表

参考文献

  • JavaEE多层架构Struts2+Spring3+Hibernate3+Ajax的整合(大连海事大学·王向兵)
  • 基于Ajax和SSH框架的高校大型设备共享系统(湖南师范大学·欧阳玲)
  • J2EE轻量级框架在预算管理系统中的应用研究(大连海事大学·车传文)
  • 基于Web服务的社会标注系统的设计与实现(大连理工大学·史梦露)
  • 基于SSH2的轻博客系统的研究与实现(吉林大学·杨雪梅)
  • 基于SSH资源管理系统的设计及实现(西安电子科技大学·杨静涛)
  • 基于Ajax和SSH框架的高校大型设备共享系统(湖南师范大学·欧阳玲)
  • 基于J2EE平台的Spring框架分析研究与应用(武汉科技大学·刘行亮)
  • 基于B/S架构的酷跑社区系统的设计与实现(内蒙古大学·张晓乐)
  • 基于SSH资源管理系统的设计及实现(西安电子科技大学·杨静涛)
  • 一种Web应用框架的设计与实现(·河北师范大学)
  • 基于Web服务的社会标注系统的设计与实现(大连理工大学·史梦露)
  • Spring框架技术分析及应用研究(中国科学院大学(工程管理与信息技术学院)·翟剑锟)
  • 基于Web服务的社会标注系统的设计与实现(大连理工大学·史梦露)
  • 基于OAuth2.0协议的企业分布式授权系统设计与实现(华中科技大学·支猛)

本文内容包括但不限于文字、数据、图表及超链接等)均来源于该信息及资料的相关主题。发布者:代码客栈 ,原文地址:https://bishedaima.com/yuanma/35573.html

相关推荐

发表回复

登录后才能评论