Java内容管理系统案例

wen java案例 3

本文目录导读:

Java内容管理系统案例

  1. 系统架构设计
  2. 核心代码实现
  3. 前端Vue组件示例
  4. 核心功能亮点
  5. 运行与部署

我将为您提供一个完整的Java内容管理系统(CMS)案例,包含核心功能代码和架构设计。

系统架构设计

技术栈

  • 后端: Spring Boot 2.7.x + MyBatis-Plus + Spring Security
  • 前端: Vue 3 + Element Plus
  • 数据库: MySQL 8.0
  • 缓存: Redis
  • 构建工具: Maven

项目结构

cms-system/
├── src/main/java/com/example/cms/
│   ├── config/          # 配置类
│   ├── controller/      # 控制器
│   ├── service/         # 业务逻辑
│   ├── mapper/          # 数据访问
│   ├── entity/          # 实体类
│   ├── dto/             # 数据传输对象
│   ├── common/          # 通用工具类
│   └── security/        # 安全配置
├── src/main/resources/
│   ├── mapper/          # MyBatis映射文件
│   ├── static/          # 静态资源
│   └── application.yml  # 配置文件
└── pom.xml

核心代码实现

实体类设计

// Article.java - 文章实体
package com.example.cms.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.List;
@Data
@TableName("cms_article")
public class Article {
    @TableId(type = IdType.AUTO)
    private Long id;
    private String title;
    private String summary;
    @TableField("content")
    private String content;
    private Long categoryId;
    private Long authorId;
    private String coverImage;
    @TableField("status")
    private Integer status; // 0-草稿 1-已发布 2-已下线
    @TableField("view_count")
    private Integer viewCount;
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
    @TableLogic
    @TableField("deleted")
    private Integer deleted;
    // 非数据库字段,用于关联查询
    @TableField(exist = false)
    private Category category;
    @TableField(exist = false)
    private List<Tag> tags;
}
// Category.java - 栏目实体
package com.example.cms.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.List;
@Data
@TableName("cms_category")
public class Category {
    @TableId(type = IdType.AUTO)
    private Long id;
    private String name;
    @TableField("slug")
    private String slug;
    private Long parentId;
    private Integer sort;
    @TableField("status")
    private Integer status;
    private LocalDateTime createTime;
    @TableField(exist = false)
    private List<Category> children;
}

文章服务层

// ArticleService.java
package com.example.cms.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.cms.dto.ArticleDTO;
import com.example.cms.entity.Article;
import com.example.cms.mapper.ArticleMapper;
import com.example.cms.vo.ArticleVO;
import org.springframework.beans.BeanUtils;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class ArticleService extends ServiceImpl<ArticleMapper, Article> {
    // 分页查询文章列表
    public IPage<ArticleVO> getArticlePage(Integer pageNum, Integer pageSize, Integer status, Long categoryId) {
        Page<Article> page = new Page<>(pageNum, pageSize);
        Page<ArticleVO> result = new Page<>(pageNum, pageSize);
        // 使用自定义SQL查询,关联分类表
        List<ArticleVO> articleVOs = baseMapper.selectArticleListWithCategory(
            page, status, categoryId
        );
        BeanUtils.copyProperties(articleVOs, result.getRecords());
        result.setTotal(page.getTotal());
        result.setPages(page.getPages());
        return result;
    }
    // 发布文章
    @Transactional
    public Long publishArticle(ArticleDTO dto) {
        Article article = new Article();
        BeanUtils.copyProperties(dto, article);
        article.setStatus(1);
        article.setViewCount(0);
        // 保存文章
        save(article);
        // 保存文章标签关联
        if (dto.getTagIds() != null && !dto.getTagIds().isEmpty()) {
            baseMapper.insertArticleTags(article.getId(), dto.getTagIds());
        }
        // 更新文章数量统计
        updateCategoryArticleCount(dto.getCategoryId(), 1);
        return article.getId();
    }
    // 更新文章
    @Transactional
    public boolean updateArticle(Long id, ArticleDTO dto) {
        Article article = getById(id);
        if (article == null) {
            return false;
        }
        // 如果分类改变,更新分类文章数量
        if (!article.getCategoryId().equals(dto.getCategoryId())) {
            updateCategoryArticleCount(article.getCategoryId(), -1);
            updateCategoryArticleCount(dto.getCategoryId(), 1);
        }
        BeanUtils.copyProperties(dto, article);
        return updateById(article);
    }
    // 删除文章(逻辑删除)
    @Transactional
    public boolean deleteArticle(Long id) {
        Article article = getById(id);
        if (article == null) {
            return false;
        }
        // 逻辑删除
        removeById(id);
        // 更新分类文章数量
        updateCategoryArticleCount(article.getCategoryId(), -1);
        return true;
    }
    // 增加浏览次数
    @Cacheable(value = "articleViewCount", key = "#articleId")
    public void incrementViewCount(Long articleId) {
        baseMapper.incrementViewCount(articleId);
    }
    // 获取热门文章
    public List<ArticleVO> getHotArticles(Integer limit) {
        return baseMapper.selectHotArticles(limit);
    }
    private void updateCategoryArticleCount(Long categoryId, int count) {
        baseMapper.updateCategoryArticleCount(categoryId, count);
    }
}

安全的API接口控制

// ArticleController.java
package com.example.cms.controller;
import com.example.cms.common.Result;
import com.example.cms.dto.ArticleDTO;
import com.example.cms.service.ArticleService;
import com.example.cms.vo.ArticleVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
@RestController
@RequestMapping("/api/articles")
public class ArticleController {
    @Autowired
    private ArticleService articleService;
    // 文章列表(公开接口)
    @GetMapping
    public Result list(@RequestParam(defaultValue = "1") Integer page,
                      @RequestParam(defaultValue = "10") Integer size,
                      @RequestParam(required = false) Integer status,
                      @RequestParam(required = false) Long categoryId) {
        return Result.success(articleService.getArticlePage(page, size, status, categoryId));
    }
    // 文章详情(公开接口)
    @GetMapping("/{id}")
    public Result detail(@PathVariable Long id) {
        // 增加浏览量
        articleService.incrementViewCount(id);
        return Result.success(articleService.getById(id));
    }
    // 发布文章(需要管理员权限)
    @PostMapping
    @PreAuthorize("hasRole('ADMIN')")
    public Result create(@Valid @RequestBody ArticleDTO dto) {
        Long articleId = articleService.publishArticle(dto);
        return Result.success("文章发布成功", articleId);
    }
    // 编辑文章(需要管理员权限)
    @PutMapping("/{id}")
    @PreAuthorize("hasRole('ADMIN')")
    public Result update(@PathVariable Long id, @Valid @RequestBody ArticleDTO dto) {
        boolean success = articleService.updateArticle(id, dto);
        return success ? Result.success("文章更新成功") : Result.error("文章不存在");
    }
    // 删除文章(需要管理员权限)
    @DeleteMapping("/{id}")
    @PreAuthorize("hasRole('ADMIN')")
    public Result delete(@PathVariable Long id) {
        boolean success = articleService.deleteArticle(id);
        return success ? Result.success("文章删除成功") : Result.error("文章不存在");
    }
    // 获取热门文章(公开接口)
    @GetMapping("/hot")
    public Result hotArticles() {
        return Result.success(articleService.getHotArticles(10));
    }
}

数据库设计

-- 文章表
CREATE TABLE `cms_article` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '文章ID', varchar(200) NOT NULL COMMENT '文章标题',
  `summary` varchar(500) DEFAULT NULL COMMENT '#39;,
  `content` longtext COMMENT '文章内容',
  `category_id` bigint(20) DEFAULT NULL COMMENT '分类ID',
  `author_id` bigint(20) DEFAULT NULL COMMENT '作者ID',
  `cover_image` varchar(500) DEFAULT NULL COMMENT '封面图',
  `status` tinyint(4) DEFAULT '0' COMMENT '状态:0-草稿 1-已发布 2-已下线',
  `view_count` int(11) DEFAULT '0' COMMENT '浏览次数',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间',
  `deleted` tinyint(1) DEFAULT '0' COMMENT '逻辑删除:0-正常 1-已删除',
  PRIMARY KEY (`id`),
  KEY `idx_category_id` (`category_id`),
  KEY `idx_status_create_time` (`status`, `create_time`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='文章表';
-- 分类表
CREATE TABLE `cms_category` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '分类ID',
  `name` varchar(100) NOT NULL COMMENT '分类名称',
  `slug` varchar(100) DEFAULT NULL COMMENT '简称',
  `parent_id` bigint(20) DEFAULT '0' COMMENT '父分类ID',
  `sort` int(11) DEFAULT '0' COMMENT '排序',
  `status` tinyint(4) DEFAULT '1' COMMENT '状态:0-禁用 1-启用',
  `article_count` int(11) DEFAULT '0' COMMENT '文章数量',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_slug` (`slug`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='分类表';
-- 标签表
CREATE TABLE `cms_tag` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '标签ID',
  `name` varchar(100) NOT NULL COMMENT '标签名称',
  `article_count` int(11) DEFAULT '0' COMMENT '文章数量',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='标签表';
-- 文章标签关联表
CREATE TABLE `cms_article_tag` (
  `article_id` bigint(20) NOT NULL,
  `tag_id` bigint(20) NOT NULL,
  PRIMARY KEY (`article_id`, `tag_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='文章标签关联表';

配置文件

# application.yml
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/cms_db?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
    username: root
    password: root123
    driver-class-name: com.mysql.cj.jdbc.Driver
  redis:
    host: localhost
    port: 6379
    password: 
    database: 0
  cache:
    type: redis
    redis:
      time-to-live: 60000
      cache-null-values: false
mybatis-plus:
  mapper-locations: classpath:/mapper/*.xml
  type-aliases-package: com.example.cms.entity
  configuration:
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    db-config:
      id-type: auto
      logic-delete-field: deleted
      logic-delete-value: 1
      logic-not-delete-value: 0
server:
  port: 8080

安全配置

// SecurityConfig.java
package com.example.cms.security;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
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.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private JwtAuthenticationFilter jwtAuthenticationFilter;
    @Autowired
    private UserDetailsServiceImpl userDetailsService;
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
            .cors().and()
            .authorizeRequests()
            .antMatchers("/api/auth/**", "/public/**").permitAll()
            .antMatchers("/api/articles").permitAll() // 公开接口
            .antMatchers("/api/admin/**").hasRole("ADMIN")
            .anyRequest().authenticated().and()
            .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
            .exceptionHandling().authenticationEntryPoint(unauthorizedHandler);
    }
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService)
            .passwordEncoder(passwordEncoder());
    }
}

前端Vue组件示例

<!-- AdminArticleList.vue -->
<template>
  <div class="article-management">
    <el-card>
      <div class="toolbar">
        <el-input 
          v-model="searchParams.title" 
          placeholder="搜索文章标题"
          style="width: 200px; margin-right: 10px;"
          clearable
        />
        <el-select v-model="searchParams.status" placeholder="状态筛选" clearable>
          <el-option label="草稿" :value="0" />
          <el-option label="已发布" :value="1" />
          <el-option label="已下线" :value="2" />
        </el-select>
        <el-button type="primary" @click="loadData">搜索</el-button>
        <el-button type="success" @click="createArticle">新建文章</el-button>
      </div>
      <el-table :data="articleList" stripe border>
        <el-table-column prop="id" label="ID" width="80" />
        <el-table-column prop="title" label="标题" min-width="200" />
        <el-table-column prop="category.name" label="分类" width="100" />
        <el-table-column prop="viewCount" label="浏览量" width="100" />
        <el-table-column label="状态" width="80">
          <template #default="scope">
            <el-tag :type="getStatusType(scope.row.status)">
              {{ getStatusLabel(scope.row.status) }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column label="操作" width="200" fixed="right">
          <template #default="scope">
            <el-button size="small" @click="editArticle(scope.row)">编辑</el-button>
            <el-button size="small" type="danger" @click="deleteArticle(scope.row)">删除</el-button>
          </template>
        </el-table-column>
      </el-table>
      <el-pagination
        v-model:current-page="searchParams.page"
        v-model:page-size="searchParams.size"
        :total="total"
        :page-sizes="[10, 20, 50]"
        layout="total, sizes, prev, pager, next"
        @change="loadData"
      />
    </el-card>
  </div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import axios from 'axios'
const articleList = ref([])
const total = ref(0)
const searchParams = reactive({ '',
  status: null,
  page: 1,
  size: 10
})
const getStatusType = (status) => {
  return status === 1 ? 'success' : status === 0 ? 'info' : 'warning'
}
const getStatusLabel = (status) => {
  return status === 1 ? '已发布' : status === 0 ? '草稿' : '已下线'
}
const loadData = async () => {
  try {
    const response = await axios.get('/api/articles', { params: searchParams })
    articleList.value = response.data.data.records
    total.value = response.data.data.total
  } catch (error) {
    ElMessage.error('加载文章列表失败')
  }
}
const createArticle = () => {
  // 跳转到编辑页面
  router.push('/admin/articles/create')
}
const editArticle = (row) => {
  router.push(`/admin/articles/edit/${row.id}`)
}
const deleteArticle = async (row) => {
  try {
    await ElMessageBox.confirm(`确定删除文章"${row.title}"?`, '提示')
    await axios.delete(`/api/articles/${row.id}`)
    ElMessage.success('文章删除成功')
    loadData()
  } catch (error) {
    if (error !== 'cancel') {
      ElMessage.error('删除失败')
    }
  }
}
onMounted(() => {
  loadData()
})
</script>
<style scoped>
.article-management {
  padding: 20px;
}
.toolbar {
  margin-bottom: 20px;
  display: flex;
  align-items: center;
}
</style>

核心功能亮点

多级分类管理

  • 支持无限级分类
  • 分类排序和权限控制
  • 分类文章数量自动统计

完整的工作流

  • 草稿、审核、发布、下线流程
  • 定时发布功能
  • 版本管理

性能优化

  • Redis缓存热门文章
  • 数据库索引优化
  • 分页查询优化

安全机制

  • Spring Security + JWT认证
  • 角色权限控制(管理员、编辑、作者)
  • 防SQL注入和XSS攻击

统计分析

  • 文章浏览量统计
  • 分类点击排行
  • 用户行为分析

运行与部署

本地运行

# 克隆项目
git clone https://github.com/your-project/cms-system.git
# 进入项目目录
cd cms-system
# 安装依赖
mvn clean install
# 运行项目
mvn spring-boot:run

Docker部署

# Dockerfile
FROM openjdk:8-jre-alpine
ADD target/cms-system.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "/app.jar"]
# 构建镜像
docker build -t cms-system .
# 运行容器
docker run -d -p 8080:8080 \
  -e DB_HOST=database \
  -e REDIS_HOST=redis \
  --link mysql:database \
  --link redis:redis \
  cms-system

初始化数据

-- 初始化管理员账号
INSERT INTO sys_user (username, password, role) 
VALUES ('admin', '$2a$10$...加密后的密码', 'ADMIN');
-- 初始化示例分类
INSERT INTO cms_category (name, slug, parent_id, sort, status) VALUES 
('技术', 'technology', 0, 1, 1),
('产品', 'product', 0, 2, 1),
('设计', 'design', 0, 3, 1);

这个案例包含了CMS系统的主要功能,您可以根据实际需求进行扩展,比如增加评论系统、用户系统、SEO优化模块等。

抱歉,评论功能暂时关闭!