基于SSM+SpringBoot+MySql+Layui的校园信息共享平台

74 篇文章 19 订阅

大家好,很高兴和大家分享源码。不管是什么样的需求。都希望各位计算机专业的同学们有一个提高。

大家可以通过常用的搜索引擎,以百度为例,搜索 源码乐园 code51 ,然后再次搜索 自己想要的即可。更多的管理系统等,欢迎大家百度搜索源码乐园。

代码展示

1.项目简介

CISP 全称 Campus Information Sharing Platform -- 校内信息共享平台

这是本科毕业设计,一个类似于论坛的信息发布平台

后端基于SpringBoot开发

前端使用LayUI框架 + freemarker动态模板生成

数据库使用MySQL

MVC三层架构

cisp

1.项目简介

CISP 全称 Campus Information Sharing Platform -- 校内信息共享平台

这是本科毕业设计,一个类似于论坛的信息发布平台

后端基于SpringBoot开发

前端使用LayUI框架 + freemarker动态模板生成

数据库使用MySQL

MVC三层架构

1.1功能逻辑

 注册

  • 用户注册成功,将用户信息存入 MySQL,但此时该用户状态为未激活
  • 向用户发送激活邮件,用户点击链接则激活账号(Spring Mail)

  1. 1:

登录 | 登出

登录认证模块跳过了 Spring Secuity 自带的认证机制。主要逻辑如下:

  • 进入登录界面,随机生成一个字符串来标识这个将要登录的用户,将这个字符串短暂的存入 Cookie(60 秒);
  • 动态生成验证码,并将验证码及标识该用户的字符串短暂存入 Redis(60 秒);
  • 为登录成功(验证用户名、密码、验证码)的用户随机生成登录凭证且设置状态为有效,并将登录凭证及其状态等信息永久存入 Redis,再在 Cookie 中存一份登录凭证;
  • 使用拦截器在所有的请求执行之前,从 Cookie 中获取登录凭证,只要 Redis 中该凭证有效并在有效期内,本次请求就会一直持有该用户信息(使用 ThreadLocal 持有用户信息,保证多台服务器上用户的登录状态同步);
  • 勾选记住我,则延长 Cookie 中登录凭证的有效时间;
  • 用户登出,将凭证状态设为无效,并更新 Redis 中该登录凭证的相关信息。

下图是登录模块的功能逻辑图,并没有使用 Spring Security 提供的认证逻辑(我觉得这个模块是最复杂的,这张图其实很多细节还没有画全)

Figure 2:

 显示评论及相关信息

评论部分前端的名称显示有些缺陷,有兴趣的小伙伴欢迎提 PR 解决 ~

关于评论模块需要注意的就是评论表的设计,把握其中字段的含义,才能透彻了解这个功能的逻辑。

评论 Comment 的目标类型(帖子,评论) entityType 和 entityId 以及对哪个用户进行评论/回复 targetId 是由前端传递给 DiscussPostController 的

Figure 3:

2.开发环境

  • 操作系统:Windows 10
  • 构建工具:Apache Maven
  • 集成开发工具:Intellij IDEA
  • 应用服务器:Apache Tomcat
  • 接口测试工具:Postman
  • 压力测试工具:Apache JMeter
  • 版本控制工具:Git
  • Java 版本:8

3.数据库设计

3.1表结构

article表

Figure 4:

category表

Figure 5:

comment表

Figure 6:

user表

Figure 7:

3.2ER图

Figure 8:

4.项目开发

4.1配置maven依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.2.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.zbin</groupId>
    <artifactId>cisp</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>cisp</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-freemarker</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.0.0</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.26</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.mindrot</groupId>
            <artifactId>jbcrypt</artifactId>
            <version>0.4</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.qiniu/qiniu-java-sdk -->
        <dependency>
            <groupId>com.qiniu</groupId>
            <artifactId>qiniu-java-sdk</artifactId>
            <version>7.2.18</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.6</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.56</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <mainClass>com.zbin.cisp.CispApplication</mainClass>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

4.2项目配置

# 配置freemarker
spring:
  freemarker:
    # 设置模板后缀名
    suffix: .html
    # 设置文档类型
    content-type: text/html
    # 设置页面编码格式
    charset: UTF-8
    # 设置页面缓存
    cache: false
    # 设置ftl文件路径
    template-loader-path:
      - classpath:/templates
  # 设置静态文件路径,js,css等
  mvc:
    static-path-pattern: /static/**
  resources:
    static-locations: ["/templates/","/static/"]
  http:
    encoding:
      charset: utf-8
      force: true
      enabled: true
  #配置数据源
  datasource:
    url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&autoReconnect=true&serverTimezone=Asia/Shanghai
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver

mybatis:
  mapper-locations: classpath*:mapper/*.xml
  type-aliases-package: com.zbin.cisp.domain
  configuration:
    map-underscore-to-camel-case: true
server:
  port: 8080

4.3拦截器实现

public class LoginInterceptor implements HandlerInterceptor {

  @Override
  public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
    throws Exception {
    boolean flag;
    if (request.getRequestURI().startsWith("/admin")
      && request.getSession().getAttribute("adminUser") == null) {
      response.sendRedirect("/admin");
      flag = false;
    } else if (request.getSession().getAttribute("user") == null
      && request.getSession().getAttribute("adminUser") == null) {
      response.sendRedirect("/login");
      flag = false;
    } else {
      flag = true;
    }
    return flag;
  }

  @Override
  public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
    ModelAndView modelAndView) throws Exception {

  }

  @Override
  public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
    Object handler, Exception ex) throws Exception {

  }

}

4.4工具类

文件处理

public class FileUtil {

  private static final String ACCESS_KEY = "pl7KvcAWGCe1eI2RPKKyrp7zxU_o8PM6rGAb7SG7";

  private static final String SECRET_KEY = "aQtmNi3Zvo_qDJ-8tBQ1tObNxJ-M95Bkr2ndIpDK";

  private static final String PREFIX_URL = "http://cdn.iwzb.top/";

  private static final String BUCKET = "cisp";

  public static String upload(MultipartFile originFile) {
    try {
      String filename = "";
      if (originFile.getOriginalFilename() != null) {
        filename = originFile.getOriginalFilename();
      }
      File file = new File(filename);
      FileUtils.copyInputStreamToFile(originFile.getInputStream(), file);
      UploadManager uploadManager = getUploadManager();

      String token = getToken();
      Response response = uploadManager.put(file.getAbsolutePath(), newName(file.getName()), token);

      //解析上传成功的结果
      DefaultPutRet putRet = new Gson().fromJson(response.bodyString(), DefaultPutRet.class);
      if (file.delete()) {
        return PREFIX_URL + putRet.key;
      }
      return null;
    } catch (Exception e) {
      e.printStackTrace();
      return null;
    }
  }

  public static boolean delete(String key) {
    try {
      BucketManager bkm = getBucketManager();
      bkm.delete(BUCKET, key);
      return true;
    } catch (Exception e) {
      return false;
    }
  }

  private static UploadManager getUploadManager() {
    Configuration cfg = new Configuration(Zone.zone0());
    return new UploadManager(cfg);
  }

  private static BucketManager getBucketManager() {
    Configuration cfg = new Configuration(Zone.zone0());
    Auth auth = Auth.create(ACCESS_KEY, SECRET_KEY);
    return new BucketManager(auth, cfg);
  }

  private static String newName(String oldName) {
    String[] datas = oldName.split("\\.");
    String type = datas[datas.length - 1];
    return UUID.randomUUID().toString() + "." + type;
  }

  private static String getToken() {
    Auth auth = Auth.create(ACCESS_KEY, SECRET_KEY);
    return auth.uploadToken(BUCKET);
  }
}

加密工具类

public class PasswordUtil {

  /**
   * 加密密码
   */
  public static String bryptPwd(String pwd) {
    return BCrypt.hashpw(pwd, BCrypt.gensalt());
  }

  /**
   * 校验密码
   */
  public static boolean validPwd(String pwd, String hashed) {
    try {
      return BCrypt.checkpw(pwd, hashed);
    } catch (Exception e) {
      e.printStackTrace();
      return false;
    }
  }
}

4.5主要功能实现

@Controller
@RequestMapping("/article")
public class ArticleController {

  @Resource
  ArticleService articleService;

  @Resource
  CategoryService categoryService;

  @Resource
  CommentService commentService;

  @Resource
  UserService userService;

  @RequestMapping("/uploadImg")
  @ResponseBody
  public ReturnJson upload(HttpServletRequest request, MultipartFile file) {
    String imgUrl = FileUtil.upload(file);
    Map<String, String> imgMap = new HashMap<>();
    imgMap.put("src", imgUrl);
    imgMap.put("title", file.getOriginalFilename());
    return new ReturnJson("上传成功", imgMap);
  }

  @RequestMapping("/add")
  @ResponseBody
  public ReturnJson add(HttpServletRequest request, @RequestBody Article article) {
    try {
      User user;
      if (request.getSession().getAttribute("adminUser") != null) {
        user = (User) request.getSession().getAttribute("adminUser");
      } else {
        user = (User) request.getSession().getAttribute("user");
      }
      if ("禁言".equals(user.getStatus())) {
        return new ReturnJson(1, "您被禁言,无法发布文章!");
      }
      article.setUserId(user.getId());
      if (article.getId() == null) {
        articleService.create(article);
      } else {
        articleService.update(article);
      }
      return new ReturnJson(0, "发布成功");
    } catch (Exception e) {
      return new ReturnJson(1, "发布失败");
    }
  }

  @RequestMapping("/addCategory")
  @ResponseBody
  public ReturnJson addCategory(HttpServletRequest request, @RequestBody Category category) {
    try {
      if (category.getName() != null) {
        categoryService.create(category);
        return new ReturnJson(0, "新增分类成功");
      } else {
        return new ReturnJson(1, "分类名不能为空");
      }
    } catch (Exception e) {
      return new ReturnJson(1, "新增分类失败");
    }
  }

  @RequestMapping("/delCategory")
  @ResponseBody
  public ReturnJson delCategory(HttpServletRequest request, @RequestBody Category category) {
    try {
      if (category.getId() != null) {
        categoryService.deleteById(category.getId());
        return new ReturnJson(0, "删除分类成功");
      } else {
        return new ReturnJson(1, "删除分类失败");
      }
    } catch (Exception e) {
      return new ReturnJson(1, "新增分类失败");
    }
  }

  @RequestMapping("/updateCategory")
  @ResponseBody
  public ReturnJson updateCategory(HttpServletRequest request, @RequestBody Category category) {
    try {
      if (category.getId() != null && category.getName() != null) {
        categoryService.updateById(category);
        return new ReturnJson(0, "修改分类成功");
      } else {
        return new ReturnJson(1, "修改分类失败");
      }
    } catch (Exception e) {
      return new ReturnJson(1, "修改分类失败");
    }
  }

  @RequestMapping("/addComment")
  @ResponseBody
  public ReturnJson addComment(@RequestBody Comment comment) {
    try {
      User user = userService.getUserById(comment.getUserId());
      if ("禁言".equals(user.getStatus())) {
        return new ReturnJson(1, "您被禁言,无法发表评论!");
      }
      commentService.create(comment);
      return new ReturnJson("评论成功");
    } catch (Exception e) {
      return new ReturnJson(1, "评论失败");
    }
  }

  @RequestMapping("/delComment")
  @ResponseBody
  public ReturnJson delComment(@RequestBody Comment comment) {
    try {
      commentService.delete(comment);
      return new ReturnJson("删除评论成功");
    } catch (Exception e) {
      return new ReturnJson(1, "删除评论失败");
    }
  }

  @RequestMapping("/delete")
  @ResponseBody
  public ReturnJson deleteArticle(@RequestBody String param) {
    try {
      JSONObject json = JSON.parseObject(param);
      Integer id = json.getInteger("id");
      articleService.delete(id);
      return new ReturnJson("删除成功");
    } catch (Exception e) {
      return new ReturnJson(1, "删除失败");
    }
  }

  @RequestMapping("/setTop")
  @ResponseBody
  public ReturnJson setTopArticle(@RequestBody String param) {
    try {
      JSONObject json = JSON.parseObject(param);
      Integer articleId = json.getInteger("value");
      articleService.setTopStatus(articleId);
      return new ReturnJson("置顶成功");
    } catch (Exception e) {
      return new ReturnJson(1, "置顶失败");
    }
  }


}

5.项目展示

5.1普通用户

主页

Figure 9:

登录页

Figure 10:

主页

Figure 11:

个人中心

Figure 12:

Figure 13:

主页文章分类

Figure 14:

文章详情

Figure 15:

Figure 16:

文章评论

Figure 17:

发表文章

Figure 18:

5.2管理员

登录

Figure 19:

主页

Figure 20:

文章管理

Figure 21:

修改文章

Figure 22:

查看文章

Figure 23:

删除文章

Figure 24:

分类管理

Figure 25:

用户管理

Figure 26:

禁言处理

Figure 27:

添加用户

Figure 28:

模糊查询

Figure 29:

数据统计

Figure 30:

1.1功能逻辑

 注册

  • 用户注册成功,将用户信息存入 MySQL,但此时该用户状态为未激活
  • 向用户发送激活邮件,用户点击链接则激活账号(Spring Mail)

  1. 1:

登录 | 登出

登录认证模块跳过了 Spring Secuity 自带的认证机制。主要逻辑如下:

  • 进入登录界面,随机生成一个字符串来标识这个将要登录的用户,将这个字符串短暂的存入 Cookie(60 秒);
  • 动态生成验证码,并将验证码及标识该用户的字符串短暂存入 Redis(60 秒);
  • 为登录成功(验证用户名、密码、验证码)的用户随机生成登录凭证且设置状态为有效,并将登录凭证及其状态等信息永久存入 Redis,再在 Cookie 中存一份登录凭证;
  • 使用拦截器在所有的请求执行之前,从 Cookie 中获取登录凭证,只要 Redis 中该凭证有效并在有效期内,本次请求就会一直持有该用户信息(使用 ThreadLocal 持有用户信息,保证多台服务器上用户的登录状态同步);
  • 勾选记住我,则延长 Cookie 中登录凭证的有效时间;
  • 用户登出,将凭证状态设为无效,并更新 Redis 中该登录凭证的相关信息。

下图是登录模块的功能逻辑图,并没有使用 Spring Security 提供的认证逻辑(我觉得这个模块是最复杂的,这张图其实很多细节还没有画全)

Figure 2:

 显示评论及相关信息

评论部分前端的名称显示有些缺陷,有兴趣的小伙伴欢迎提 PR 解决 ~

关于评论模块需要注意的就是评论表的设计,把握其中字段的含义,才能透彻了解这个功能的逻辑。

评论 Comment 的目标类型(帖子,评论) entityType 和 entityId 以及对哪个用户进行评论/回复 targetId 是由前端传递给 DiscussPostController 的

Figure 3:

2.开发环境

  • 操作系统:Windows 10
  • 构建工具:Apache Maven
  • 集成开发工具:Intellij IDEA
  • 应用服务器:Apache Tomcat
  • 接口测试工具:Postman
  • 压力测试工具:Apache JMeter
  • 版本控制工具:Git
  • Java 版本:8

3.数据库设计

3.1表结构

article表

Figure 4:

category表

Figure 5:

comment表

Figure 6:

user表

Figure 7:

3.2ER图

Figure 8:

4.项目开发

4.1配置maven依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.2.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.zbin</groupId>
    <artifactId>cisp</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>cisp</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-freemarker</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.0.0</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.26</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.mindrot</groupId>
            <artifactId>jbcrypt</artifactId>
            <version>0.4</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.qiniu/qiniu-java-sdk -->
        <dependency>
            <groupId>com.qiniu</groupId>
            <artifactId>qiniu-java-sdk</artifactId>
            <version>7.2.18</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.6</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.56</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <mainClass>com.zbin.cisp.CispApplication</mainClass>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

4.2项目配置

# 配置freemarker
spring:
  freemarker:
    # 设置模板后缀名
    suffix: .html
    # 设置文档类型
    content-type: text/html
    # 设置页面编码格式
    charset: UTF-8
    # 设置页面缓存
    cache: false
    # 设置ftl文件路径
    template-loader-path:
      - classpath:/templates
  # 设置静态文件路径,js,css等
  mvc:
    static-path-pattern: /static/**
  resources:
    static-locations: ["/templates/","/static/"]
  http:
    encoding:
      charset: utf-8
      force: true
      enabled: true
  #配置数据源
  datasource:
    url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&autoReconnect=true&serverTimezone=Asia/Shanghai
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver

mybatis:
  mapper-locations: classpath*:mapper/*.xml
  type-aliases-package: com.zbin.cisp.domain
  configuration:
    map-underscore-to-camel-case: true
server:
  port: 8080

4.3拦截器实现

public class LoginInterceptor implements HandlerInterceptor {

  @Override
  public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
    throws Exception {
    boolean flag;
    if (request.getRequestURI().startsWith("/admin")
      && request.getSession().getAttribute("adminUser") == null) {
      response.sendRedirect("/admin");
      flag = false;
    } else if (request.getSession().getAttribute("user") == null
      && request.getSession().getAttribute("adminUser") == null) {
      response.sendRedirect("/login");
      flag = false;
    } else {
      flag = true;
    }
    return flag;
  }

  @Override
  public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
    ModelAndView modelAndView) throws Exception {

  }

  @Override
  public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
    Object handler, Exception ex) throws Exception {

  }

}

4.4工具类

文件处理

public class FileUtil {

  private static final String ACCESS_KEY = "pl7KvcAWGCe1eI2RPKKyrp7zxU_o8PM6rGAb7SG7";

  private static final String SECRET_KEY = "aQtmNi3Zvo_qDJ-8tBQ1tObNxJ-M95Bkr2ndIpDK";

  private static final String PREFIX_URL = "http://cdn.iwzb.top/";

  private static final String BUCKET = "cisp";

  public static String upload(MultipartFile originFile) {
    try {
      String filename = "";
      if (originFile.getOriginalFilename() != null) {
        filename = originFile.getOriginalFilename();
      }
      File file = new File(filename);
      FileUtils.copyInputStreamToFile(originFile.getInputStream(), file);
      UploadManager uploadManager = getUploadManager();

      String token = getToken();
      Response response = uploadManager.put(file.getAbsolutePath(), newName(file.getName()), token);

      //解析上传成功的结果
      DefaultPutRet putRet = new Gson().fromJson(response.bodyString(), DefaultPutRet.class);
      if (file.delete()) {
        return PREFIX_URL + putRet.key;
      }
      return null;
    } catch (Exception e) {
      e.printStackTrace();
      return null;
    }
  }

  public static boolean delete(String key) {
    try {
      BucketManager bkm = getBucketManager();
      bkm.delete(BUCKET, key);
      return true;
    } catch (Exception e) {
      return false;
    }
  }

  private static UploadManager getUploadManager() {
    Configuration cfg = new Configuration(Zone.zone0());
    return new UploadManager(cfg);
  }

  private static BucketManager getBucketManager() {
    Configuration cfg = new Configuration(Zone.zone0());
    Auth auth = Auth.create(ACCESS_KEY, SECRET_KEY);
    return new BucketManager(auth, cfg);
  }

  private static String newName(String oldName) {
    String[] datas = oldName.split("\\.");
    String type = datas[datas.length - 1];
    return UUID.randomUUID().toString() + "." + type;
  }

  private static String getToken() {
    Auth auth = Auth.create(ACCESS_KEY, SECRET_KEY);
    return auth.uploadToken(BUCKET);
  }
}

加密工具类

public class PasswordUtil {

  /**
   * 加密密码
   */
  public static String bryptPwd(String pwd) {
    return BCrypt.hashpw(pwd, BCrypt.gensalt());
  }

  /**
   * 校验密码
   */
  public static boolean validPwd(String pwd, String hashed) {
    try {
      return BCrypt.checkpw(pwd, hashed);
    } catch (Exception e) {
      e.printStackTrace();
      return false;
    }
  }
}

4.5主要功能实现

@Controller
@RequestMapping("/article")
public class ArticleController {

  @Resource
  ArticleService articleService;

  @Resource
  CategoryService categoryService;

  @Resource
  CommentService commentService;

  @Resource
  UserService userService;

  @RequestMapping("/uploadImg")
  @ResponseBody
  public ReturnJson upload(HttpServletRequest request, MultipartFile file) {
    String imgUrl = FileUtil.upload(file);
    Map<String, String> imgMap = new HashMap<>();
    imgMap.put("src", imgUrl);
    imgMap.put("title", file.getOriginalFilename());
    return new ReturnJson("上传成功", imgMap);
  }

  @RequestMapping("/add")
  @ResponseBody
  public ReturnJson add(HttpServletRequest request, @RequestBody Article article) {
    try {
      User user;
      if (request.getSession().getAttribute("adminUser") != null) {
        user = (User) request.getSession().getAttribute("adminUser");
      } else {
        user = (User) request.getSession().getAttribute("user");
      }
      if ("禁言".equals(user.getStatus())) {
        return new ReturnJson(1, "您被禁言,无法发布文章!");
      }
      article.setUserId(user.getId());
      if (article.getId() == null) {
        articleService.create(article);
      } else {
        articleService.update(article);
      }
      return new ReturnJson(0, "发布成功");
    } catch (Exception e) {
      return new ReturnJson(1, "发布失败");
    }
  }

  @RequestMapping("/addCategory")
  @ResponseBody
  public ReturnJson addCategory(HttpServletRequest request, @RequestBody Category category) {
    try {
      if (category.getName() != null) {
        categoryService.create(category);
        return new ReturnJson(0, "新增分类成功");
      } else {
        return new ReturnJson(1, "分类名不能为空");
      }
    } catch (Exception e) {
      return new ReturnJson(1, "新增分类失败");
    }
  }

  @RequestMapping("/delCategory")
  @ResponseBody
  public ReturnJson delCategory(HttpServletRequest request, @RequestBody Category category) {
    try {
      if (category.getId() != null) {
        categoryService.deleteById(category.getId());
        return new ReturnJson(0, "删除分类成功");
      } else {
        return new ReturnJson(1, "删除分类失败");
      }
    } catch (Exception e) {
      return new ReturnJson(1, "新增分类失败");
    }
  }

  @RequestMapping("/updateCategory")
  @ResponseBody
  public ReturnJson updateCategory(HttpServletRequest request, @RequestBody Category category) {
    try {
      if (category.getId() != null && category.getName() != null) {
        categoryService.updateById(category);
        return new ReturnJson(0, "修改分类成功");
      } else {
        return new ReturnJson(1, "修改分类失败");
      }
    } catch (Exception e) {
      return new ReturnJson(1, "修改分类失败");
    }
  }

  @RequestMapping("/addComment")
  @ResponseBody
  public ReturnJson addComment(@RequestBody Comment comment) {
    try {
      User user = userService.getUserById(comment.getUserId());
      if ("禁言".equals(user.getStatus())) {
        return new ReturnJson(1, "您被禁言,无法发表评论!");
      }
      commentService.create(comment);
      return new ReturnJson("评论成功");
    } catch (Exception e) {
      return new ReturnJson(1, "评论失败");
    }
  }

  @RequestMapping("/delComment")
  @ResponseBody
  public ReturnJson delComment(@RequestBody Comment comment) {
    try {
      commentService.delete(comment);
      return new ReturnJson("删除评论成功");
    } catch (Exception e) {
      return new ReturnJson(1, "删除评论失败");
    }
  }

  @RequestMapping("/delete")
  @ResponseBody
  public ReturnJson deleteArticle(@RequestBody String param) {
    try {
      JSONObject json = JSON.parseObject(param);
      Integer id = json.getInteger("id");
      articleService.delete(id);
      return new ReturnJson("删除成功");
    } catch (Exception e) {
      return new ReturnJson(1, "删除失败");
    }
  }

  @RequestMapping("/setTop")
  @ResponseBody
  public ReturnJson setTopArticle(@RequestBody String param) {
    try {
      JSONObject json = JSON.parseObject(param);
      Integer articleId = json.getInteger("value");
      articleService.setTopStatus(articleId);
      return new ReturnJson("置顶成功");
    } catch (Exception e) {
      return new ReturnJson(1, "置顶失败");
    }
  }


}

5.项目展示

5.1普通用户

主页

Figure 9:

登录页

Figure 10:

主页

Figure 11:

个人中心

Figure 12:

Figure 13:

主页文章分类

Figure 14:

文章详情

Figure 15:

Figure 16:

文章评论

Figure 17:

发表文章

Figure 18:

5.2管理员

登录

Figure 19:

主页

Figure 20:

文章管理

Figure 21:

修改文章

Figure 22:

查看文章

Figure 23:

删除文章

Figure 24:

分类管理

Figure 25:

用户管理

Figure 26:

禁言处理

Figure 27:

添加用户

Figure 28:

模糊查询

Figure 29:

数据统计

Figure 30:

cisp

1.项目简介

CISP 全称 Campus Information Sharing Platform -- 校内信息共享平台

这是本科毕业设计,一个类似于论坛的信息发布平台

后端基于SpringBoot开发

前端使用LayUI框架 + freemarker动态模板生成

数据库使用MySQL

MVC三层架构

1.1功能逻辑

 注册

  • 用户注册成功,将用户信息存入 MySQL,但此时该用户状态为未激活
  • 向用户发送激活邮件,用户点击链接则激活账号(Spring Mail)

  1. 1:

登录 | 登出

登录认证模块跳过了 Spring Secuity 自带的认证机制。主要逻辑如下:

  • 进入登录界面,随机生成一个字符串来标识这个将要登录的用户,将这个字符串短暂的存入 Cookie(60 秒);
  • 动态生成验证码,并将验证码及标识该用户的字符串短暂存入 Redis(60 秒);
  • 为登录成功(验证用户名、密码、验证码)的用户随机生成登录凭证且设置状态为有效,并将登录凭证及其状态等信息永久存入 Redis,再在 Cookie 中存一份登录凭证;
  • 使用拦截器在所有的请求执行之前,从 Cookie 中获取登录凭证,只要 Redis 中该凭证有效并在有效期内,本次请求就会一直持有该用户信息(使用 ThreadLocal 持有用户信息,保证多台服务器上用户的登录状态同步);
  • 勾选记住我,则延长 Cookie 中登录凭证的有效时间;
  • 用户登出,将凭证状态设为无效,并更新 Redis 中该登录凭证的相关信息。

下图是登录模块的功能逻辑图,并没有使用 Spring Security 提供的认证逻辑(我觉得这个模块是最复杂的,这张图其实很多细节还没有画全)

Figure 2:

 显示评论及相关信息

评论部分前端的名称显示有些缺陷,有兴趣的小伙伴欢迎提 PR 解决 ~

关于评论模块需要注意的就是评论表的设计,把握其中字段的含义,才能透彻了解这个功能的逻辑。

评论 Comment 的目标类型(帖子,评论) entityType 和 entityId 以及对哪个用户进行评论/回复 targetId 是由前端传递给 DiscussPostController 的

Figure 3:

2.开发环境

  • 操作系统:Windows 10
  • 构建工具:Apache Maven
  • 集成开发工具:Intellij IDEA
  • 应用服务器:Apache Tomcat
  • 接口测试工具:Postman
  • 压力测试工具:Apache JMeter
  • 版本控制工具:Git
  • Java 版本:8

3.数据库设计

3.1表结构

article表

Figure 4:

category表

Figure 5:

comment表

Figure 6:

user表

Figure 7:

3.2ER图

Figure 8:

4.项目开发

4.1配置maven依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.2.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.zbin</groupId>
    <artifactId>cisp</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>cisp</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-freemarker</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.0.0</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.26</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.mindrot</groupId>
            <artifactId>jbcrypt</artifactId>
            <version>0.4</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.qiniu/qiniu-java-sdk -->
        <dependency>
            <groupId>com.qiniu</groupId>
            <artifactId>qiniu-java-sdk</artifactId>
            <version>7.2.18</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.6</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.56</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <mainClass>com.zbin.cisp.CispApplication</mainClass>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

4.2项目配置

# 配置freemarker
spring:
  freemarker:
    # 设置模板后缀名
    suffix: .html
    # 设置文档类型
    content-type: text/html
    # 设置页面编码格式
    charset: UTF-8
    # 设置页面缓存
    cache: false
    # 设置ftl文件路径
    template-loader-path:
      - classpath:/templates
  # 设置静态文件路径,js,css等
  mvc:
    static-path-pattern: /static/**
  resources:
    static-locations: ["/templates/","/static/"]
  http:
    encoding:
      charset: utf-8
      force: true
      enabled: true
  #配置数据源
  datasource:
    url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&autoReconnect=true&serverTimezone=Asia/Shanghai
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver

mybatis:
  mapper-locations: classpath*:mapper/*.xml
  type-aliases-package: com.zbin.cisp.domain
  configuration:
    map-underscore-to-camel-case: true
server:
  port: 8080

4.3拦截器实现

public class LoginInterceptor implements HandlerInterceptor {

  @Override
  public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
    throws Exception {
    boolean flag;
    if (request.getRequestURI().startsWith("/admin")
      && request.getSession().getAttribute("adminUser") == null) {
      response.sendRedirect("/admin");
      flag = false;
    } else if (request.getSession().getAttribute("user") == null
      && request.getSession().getAttribute("adminUser") == null) {
      response.sendRedirect("/login");
      flag = false;
    } else {
      flag = true;
    }
    return flag;
  }

  @Override
  public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
    ModelAndView modelAndView) throws Exception {

  }

  @Override
  public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
    Object handler, Exception ex) throws Exception {

  }

}

4.4工具类

文件处理

public class FileUtil {

  private static final String ACCESS_KEY = "pl7KvcAWGCe1eI2RPKKyrp7zxU_o8PM6rGAb7SG7";

  private static final String SECRET_KEY = "aQtmNi3Zvo_qDJ-8tBQ1tObNxJ-M95Bkr2ndIpDK";

  private static final String PREFIX_URL = "http://cdn.iwzb.top/";

  private static final String BUCKET = "cisp";

  public static String upload(MultipartFile originFile) {
    try {
      String filename = "";
      if (originFile.getOriginalFilename() != null) {
        filename = originFile.getOriginalFilename();
      }
      File file = new File(filename);
      FileUtils.copyInputStreamToFile(originFile.getInputStream(), file);
      UploadManager uploadManager = getUploadManager();

      String token = getToken();
      Response response = uploadManager.put(file.getAbsolutePath(), newName(file.getName()), token);

      //解析上传成功的结果
      DefaultPutRet putRet = new Gson().fromJson(response.bodyString(), DefaultPutRet.class);
      if (file.delete()) {
        return PREFIX_URL + putRet.key;
      }
      return null;
    } catch (Exception e) {
      e.printStackTrace();
      return null;
    }
  }

  public static boolean delete(String key) {
    try {
      BucketManager bkm = getBucketManager();
      bkm.delete(BUCKET, key);
      return true;
    } catch (Exception e) {
      return false;
    }
  }

  private static UploadManager getUploadManager() {
    Configuration cfg = new Configuration(Zone.zone0());
    return new UploadManager(cfg);
  }

  private static BucketManager getBucketManager() {
    Configuration cfg = new Configuration(Zone.zone0());
    Auth auth = Auth.create(ACCESS_KEY, SECRET_KEY);
    return new BucketManager(auth, cfg);
  }

  private static String newName(String oldName) {
    String[] datas = oldName.split("\\.");
    String type = datas[datas.length - 1];
    return UUID.randomUUID().toString() + "." + type;
  }

  private static String getToken() {
    Auth auth = Auth.create(ACCESS_KEY, SECRET_KEY);
    return auth.uploadToken(BUCKET);
  }
}

加密工具类

public class PasswordUtil {

  /**
   * 加密密码
   */
  public static String bryptPwd(String pwd) {
    return BCrypt.hashpw(pwd, BCrypt.gensalt());
  }

  /**
   * 校验密码
   */
  public static boolean validPwd(String pwd, String hashed) {
    try {
      return BCrypt.checkpw(pwd, hashed);
    } catch (Exception e) {
      e.printStackTrace();
      return false;
    }
  }
}

4.5主要功能实现

@Controller
@RequestMapping("/article")
public class ArticleController {

  @Resource
  ArticleService articleService;

  @Resource
  CategoryService categoryService;

  @Resource
  CommentService commentService;

  @Resource
  UserService userService;

  @RequestMapping("/uploadImg")
  @ResponseBody
  public ReturnJson upload(HttpServletRequest request, MultipartFile file) {
    String imgUrl = FileUtil.upload(file);
    Map<String, String> imgMap = new HashMap<>();
    imgMap.put("src", imgUrl);
    imgMap.put("title", file.getOriginalFilename());
    return new ReturnJson("上传成功", imgMap);
  }

  @RequestMapping("/add")
  @ResponseBody
  public ReturnJson add(HttpServletRequest request, @RequestBody Article article) {
    try {
      User user;
      if (request.getSession().getAttribute("adminUser") != null) {
        user = (User) request.getSession().getAttribute("adminUser");
      } else {
        user = (User) request.getSession().getAttribute("user");
      }
      if ("禁言".equals(user.getStatus())) {
        return new ReturnJson(1, "您被禁言,无法发布文章!");
      }
      article.setUserId(user.getId());
      if (article.getId() == null) {
        articleService.create(article);
      } else {
        articleService.update(article);
      }
      return new ReturnJson(0, "发布成功");
    } catch (Exception e) {
      return new ReturnJson(1, "发布失败");
    }
  }

  @RequestMapping("/addCategory")
  @ResponseBody
  public ReturnJson addCategory(HttpServletRequest request, @RequestBody Category category) {
    try {
      if (category.getName() != null) {
        categoryService.create(category);
        return new ReturnJson(0, "新增分类成功");
      } else {
        return new ReturnJson(1, "分类名不能为空");
      }
    } catch (Exception e) {
      return new ReturnJson(1, "新增分类失败");
    }
  }

  @RequestMapping("/delCategory")
  @ResponseBody
  public ReturnJson delCategory(HttpServletRequest request, @RequestBody Category category) {
    try {
      if (category.getId() != null) {
        categoryService.deleteById(category.getId());
        return new ReturnJson(0, "删除分类成功");
      } else {
        return new ReturnJson(1, "删除分类失败");
      }
    } catch (Exception e) {
      return new ReturnJson(1, "新增分类失败");
    }
  }

  @RequestMapping("/updateCategory")
  @ResponseBody
  public ReturnJson updateCategory(HttpServletRequest request, @RequestBody Category category) {
    try {
      if (category.getId() != null && category.getName() != null) {
        categoryService.updateById(category);
        return new ReturnJson(0, "修改分类成功");
      } else {
        return new ReturnJson(1, "修改分类失败");
      }
    } catch (Exception e) {
      return new ReturnJson(1, "修改分类失败");
    }
  }

  @RequestMapping("/addComment")
  @ResponseBody
  public ReturnJson addComment(@RequestBody Comment comment) {
    try {
      User user = userService.getUserById(comment.getUserId());
      if ("禁言".equals(user.getStatus())) {
        return new ReturnJson(1, "您被禁言,无法发表评论!");
      }
      commentService.create(comment);
      return new ReturnJson("评论成功");
    } catch (Exception e) {
      return new ReturnJson(1, "评论失败");
    }
  }

  @RequestMapping("/delComment")
  @ResponseBody
  public ReturnJson delComment(@RequestBody Comment comment) {
    try {
      commentService.delete(comment);
      return new ReturnJson("删除评论成功");
    } catch (Exception e) {
      return new ReturnJson(1, "删除评论失败");
    }
  }

  @RequestMapping("/delete")
  @ResponseBody
  public ReturnJson deleteArticle(@RequestBody String param) {
    try {
      JSONObject json = JSON.parseObject(param);
      Integer id = json.getInteger("id");
      articleService.delete(id);
      return new ReturnJson("删除成功");
    } catch (Exception e) {
      return new ReturnJson(1, "删除失败");
    }
  }

  @RequestMapping("/setTop")
  @ResponseBody
  public ReturnJson setTopArticle(@RequestBody String param) {
    try {
      JSONObject json = JSON.parseObject(param);
      Integer articleId = json.getInteger("value");
      articleService.setTopStatus(articleId);
      return new ReturnJson("置顶成功");
    } catch (Exception e) {
      return new ReturnJson(1, "置顶失败");
    }
  }


}

5.项目展示

5.1普通用户

主页

Figure 9:

登录页

Figure 10:

主页

Figure 11:

个人中心

Figure 12:

Figure 13:

主页文章分类

Figure 14:

文章详情

Figure 15:

Figure 16:

文章评论

Figure 17:

发表文章

Figure 18:

5.2管理员

登录

Figure 19:

主页

Figure 20:

文章管理

Figure 21:

修改文章

Figure 22:

查看文章

Figure 23:

删除文章

Figure 24:

分类管理

Figure 25:

用户管理

Figure 26:

禁言处理

Figure 27:

添加用户

Figure 28:

模糊查询

Figure 29:

数据统计

Figure 30:

项目运行截图

资料说明

基于SSM+SpringBoot+MySql+Layui的校园信息共享平台,分为前后端管理,前后用户查看,登陆注册账户,后端管理用户发布文章等内容。

  • 0
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值