基于Spring Boot + MyBatis-Plus + Thymeleaf的评论管理系统深度解析

  Java   38分钟   3894浏览   6评论

你好呀,我是小邹。

个人博客系统日渐完善,现在的文章评论以及留言数量逐渐增多,所以今天重构了管理后台的评论列表(全量查询 -> 分页条件搜索)

示例图

网页端

手机端

一、系统架构设计与技术选型

系统采用前后端分离架构,但后端保留模板渲染能力(Thymeleaf),兼顾管理后台的快速开发与前后端协作的灵活性。核心选型如下:

层次 技术/框架 选择理由
后端 Spring Boot 2.7.x 简化配置、自动装配、内置Tomcat,快速构建生产级应用
ORM MyBatis-Plus 3.5.x 基于MyBatis的增强工具,提供CRUD简化、分页插件、代码生成器,提升开发效率
缓存 Redis 高频数据缓存(如评论列表)、分布式会话管理,降低数据库压力
前端模板 Thymeleaf 支持服务端渲染,保留HTML原生结构,便于SEO和浏览器兼容
UI框架 Bootstrap 5 + Font Awesome 响应式布局、移动优先设计,图标库丰富交互体验
数据校验 Hibernate Validator 注解驱动的参数校验,统一处理请求参数合法性
2. 分层架构设计

img

  • 客户端层:支持PC(Chrome/Firefox)与移动端(iOS/Android),通过响应式设计适配不同屏幕。
  • 网关层:Nginx负载均衡,静态资源缓存(expires 30d;),反向代理后端服务。
  • 应用层:
    • Controller:处理HTTP请求,参数校验,调用Service,返回视图或JSON。
    • Service:业务逻辑封装(如评论状态变更、批量操作),事务管理(@Transactional)。
    • Mapper:MyBatis-Plus接口,通过XML/注解定义SQL,与数据库交互。
  • 数据层:MySQL存储评论数据(comments表),Redis缓存高频访问的评论列表。

二、后端核心模块深度解析

1. 分页查询与动态条件构造
(1)分页参数处理
@GetMapping
public String viewComments(Model model, 
                          @RequestParam(defaultValue = "1") Integer pageNum,
                          @RequestParam(defaultValue = "10") Integer pageSize,
                          @RequestParam(required = false) Integer status,
                          @RequestParam(required = false) String keyword) {

    // 构造分页对象(MyBatis-Plus内置Page)
    Page<Comments> page = new Page<>(pageNum, pageSize);

    // 动态查询条件构造(LambdaQueryWrapper类型安全)
    LambdaQueryWrapper<Comments> queryWrapper = new LambdaQueryWrapper<>();

    // 状态筛选:status为null时不生效,>=0避免非法参数
    if (status != null && status >= 0) {
        queryWrapper.eq(Comments::getIsVisible, status); // is_visible字段存储审核状态(0未审核/1已发布)
    }

    // 关键词搜索:昵称/内容/IP三选一匹配(使用AND连接OR条件)
    if (StringUtils.hasText(keyword)) { // StringUtils来自Spring Util,判断非空且非空白
        queryWrapper.and(wrapper -> 
            wrapper.like(Comments::getNickname, keyword)  // 昵称模糊匹配
                  .or()                                   // 或
                  .like(Comments::getContent, keyword)    // 内容模糊匹配
                  .or()                                   // 或
                  .like(Comments::getIp, keyword)         // IP模糊匹配
        );
    }

    // 排序规则:优先展示待审核评论(is_visible=0在前),按创建时间倒序
    queryWrapper.orderByAsc(Comments::getIsVisible)  // 升序:0在1前
               .orderByDesc(Comments::getCreateTime); // 降序:最新评论在前

    // 执行分页查询(MyBatis-Plus自动转换为LIMIT/OFFSET)
    IPage<Comments> commentPage = commentService.page(page, queryWrapper);

    // 传递数据到前端模板
    model.addAttribute("commentPage", commentPage);
    model.addAttribute("currentPage", pageNum);
    model.addAttribute("pageSize", pageSize);
    model.addAttribute("totalPages", commentPage.getPages()); // 总页数
    model.addAttribute("status", status); // 回显筛选状态
    model.addAttribute("keyword", keyword != null ? keyword : ""); // 回显关键词(避免空值)

    return "admin/admin_comments"; // 返回Thymeleaf模板路径
}
(2)分页插件原理

MyBatis-Plus的分页功能通过PaginationInnerInterceptor实现,核心流程如下:

  1. 参数解析:从请求中获取pageNumpageSize,生成Page对象。
  2. SQL改写:拦截原始SQL,添加LIMIT offset, size(MySQL)或OFFSET offset ROWS FETCH NEXT size ROWS ONLY(Oracle)。
  3. 总记录数查询:执行SELECT COUNT(*) FROM ...获取总数据量,计算总页数。
  4. 结果封装:将查询结果封装为IPage对象,包含当前页数据、总条数、总页数等信息。

优化点:在application.yml中配置分页插件,限制最大分页大小防止内存溢出:

mybatis-plus:
  configuration:
    map-underscore-to-camel-case: true # 驼峰命名自动映射
  global-config:
    db-config:
      logic-delete-field: is_deleted   # 逻辑删除字段(示例)
      logic-delete-value: 1            # 删除值
      logic-not-delete-value: 0        # 未删除值
  plugins:
    pagination:
      interceptors: 
        - com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor
      type: MYSQL                    # 数据库类型
      pageSizeZero: false            # pageSize=0返回全部
      maxPageSize: 100               # 最大单页数据量
2. 批量操作与事务管理
(1)批量操作接口实现
@PostMapping("/batchOperation")
public ResponseEntity<Void> batchOperation(
    @RequestParam("ids[]") List<Integer> ids,  // 前端传递的ID数组
    @RequestParam("operation") String operation) { // 操作类型(delete/visible/invisible)

    try {
        if (ids == null || ids.isEmpty()) {
            return ResponseEntity.badRequest().build(); // 参数校验:ID不能为空
        }

        switch (operation.toLowerCase()) {
            case "delete":
                // 批量删除(调用MyBatis-Plus的removeBatchByIds)
                commentService.removeBatchByIds(ids);
                break;
            case "visible":
                // 批量通过审核(更新is_visible=1)
                updateBatchStatus(ids, CommentStatus.VISIBLE.getStatus());
                break;
            case "invisible":
                // 批量屏蔽(更新is_visible=0)
                updateBatchStatus(ids, CommentStatus.INVISIBLE.getStatus());
                break;
            default:
                return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(null); // 非法操作类型
        }

        flushRedis(); // 操作后刷新缓存
        return ResponseEntity.ok().build();
    } catch (Exception e) {
        log.error("批量操作失败:{}", e.getMessage(), e); // 记录完整异常栈
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
    }
}

/**
 * 批量更新评论状态
 */
private void updateBatchStatus(List<Integer> ids, Integer status) {
    UpdateWrapper<Comments> wrapper = new UpdateWrapper<>();
    wrapper.in(Comments::getId, ids)          // ID在指定列表中
          .set(Comments::getIsVisible, status) // 更新is_visible字段
          .set(Comments::getUpdateTime, new Date()); // 更新时间戳
    commentService.update(wrapper); // MyBatis-Plus的update方法(自动生成UPDATE语句)
}
(2)事务与原子性保证
  • 事务注解:在Service层方法添加@Transactional(rollbackFor = Exception.class),确保批量操作要么全部成功,要么全部回滚。
  • 幂等性设计:通过数据库唯一索引(如id字段)避免重复操作,前端按钮添加防重复点击(disabled属性)。
  • 异常处理:捕获SqlException等数据库异常,记录日志并返回500状态码,前端提示“操作失败,请重试”。
3. 缓存策略与数据一致性
(1)Redis缓存刷新
private void flushRedis() {
    // 使用RedisCallback执行原生Redis命令(清空当前数据库)
    redisTemplate.execute((RedisCallback<Void>) connection -> {
        connection.flushDb(); // 清空DB(根据业务需求可改为按Key前缀删除)
        return null;
    });
}

设计考量

  • 全量刷新:评论数据变更后(增/删/改),清空整个Redis数据库,确保下次查询获取最新数据。适用于数据实时性要求高的场景。
  • 优化方向:若数据量极大,可改为按blog_id等维度删除特定Key(如comments:blog:123),减少缓存击穿风险。
(2)缓存重建策略
  • 懒加载:首次访问时若缓存不存在,从数据库加载并写入缓存(设置过期时间,如30分钟)。
  • 预加载:定时任务(如Quartz)每隔1小时刷新热门博客的评论缓存,减轻高峰期数据库压力。

三、前端实现细节与交互优化

1. 响应式布局的CSS实现
(1)媒体查询与布局切换
/* PC端表格视图(默认显示) */
.table-container {
    display: block;
}

/* 移动端卡片视图(默认隐藏) */
.comments-cards {
    display: none;
}

/* 媒体查询:屏幕宽度≤768px时切换为卡片视图 */
@media (max-width: 768px) {
    .table-container {
        display: none; /* 隐藏表格 */
    }

    .comments-cards {
        display: flex; /* 显示卡片 */
        flex-direction: column;
        gap: 20px;
    }
}

关键技术点

  • 使用display: none/display: flex控制视图切换,避免visibility: hidden导致的布局抖动。
  • Flex布局(flex-direction: column)实现卡片的垂直排列,适配移动端屏幕。
(2)固定列宽与表格滚动
.table-container table {
    width: 100%;
    min-width: 1000px; /* 最小宽度防止内容挤压 */
    table-layout: fixed; /* 固定列宽(不根据内容自动扩展) */
    border-collapse: separate; /* 边框分离(解决边框重叠问题) */
    border-spacing: 0; /* 边框间距为0 */
}

/* 列宽定义(示例) */
.table-container th:nth-child(1) { width: 40px; }   /* 复选框列 */
.table-container th:nth-child(2) { width: 120px; }  /* 昵称列 */
.table-container th:nth-child(3) { width: 40%; }    /* 内容列 */
.table-container th:nth-child(4) { width: 180px; }  /* 时间列 */
.table-container th:nth-child(5) { width: 120px; }  /* IP属地列 */
.table-container th:nth-child(6) { width: 100px; }  /* 操作列 */

优势

  • 固定列宽避免表格在PC端因内容过长导致列宽错乱。
  • table-layout: fixed提升渲染性能(浏览器无需计算列宽)。
2. 交互增强与用户体验
(1)评论内容展开/收起
<!-- 桌面端内容容器 -->
<div class="content-cell">
    <div th:attr="id='desktop-content-${comment.id}'" 
         class="comment-content desktop-comment"
         th:text="${comment.content}">
        <!-- Thymeleaf渲染原始内容 -->
    </div>
    <div class="toggle-expand" 
         th:attr="data-id=${comment.id}, data-type='desktop'"
         onclick="toggleExpand(this)">
        <i class="fas fa-angle-down"></i> 展开完整内容
    </div>
</div>

<!-- 移动端内容容器 -->
<div class="comment-card" th:each="comment : ${commentPage.records}">
    <div class="card-cell">
        <div th:attr="id='mobile-content-${comment.id}'" 
             class="comment-content mobile-comment"
             th:text="${comment.content}">
        </div>
        <div class="toggle-expand" 
             th:attr="data-id=${comment.id}, data-type='mobile'"
             onclick="toggleExpand(this)">
            <i class="fas fa-angle-down"></i> 展开完整内容
        </div>
    </div>
</div>
// 展开/收起功能实现
function toggleExpand(element) {
    const id = element.dataset.id;       // 获取评论ID(data-id属性)
    const type = element.dataset.type;   // 获取设备类型(desktop/mobile)
    const contentEl = document.getElementById(`${type}-content-${id}`); // 定位内容元素

    // 切换展开状态
    contentEl.classList.toggle('expanded');

    // 更新按钮文本和图标
    if (contentEl.classList.contains('expanded')) {
        element.innerHTML = '<i class="fas fa-angle-up"></i> 收起内容'; // 展开后显示收起按钮
    } else {
        element.innerHTML = '<i class="fas fa-angle-down"></i> 展开内容'; // 未展开时显示展开按钮
    }
}

CSS动画优化

/* 展开/收起的过渡效果 */
.comment-content {
    transition: max-height 0.3s ease-in-out; /* 平滑过渡高度变化 */
    overflow: hidden; /* 隐藏超出部分 */
}

.desktop-comment:not(.expanded) {
    max-height: 60px; /* 未展开时最大高度 */
}

.mobile-comment:not(.expanded) {
    max-height: 80px; /* 移动端未展开时最大高度 */
}

.comment-content.expanded {
    max-height: none; /* 展开时无高度限制 */
}
(2)批量操作的按钮状态管理
// 监听复选框变化,更新批量按钮状态
const rowCheckboxes = document.querySelectorAll('.row-checkbox');
const batchButtons = document.querySelectorAll('#batchDelete, #batchApprove, #batchReject');

function updateBatchButtonState() {
    const hasChecked = Array.from(rowCheckboxes).some(checkbox => checkbox.checked);
    batchButtons.forEach(button => {
        button.disabled = !hasChecked; // 无选中项时禁用按钮
        button.setAttribute('aria-disabled', !hasChecked); // 辅助技术支持
    });
}

// 为每个复选框添加change事件监听
rowCheckboxes.forEach(checkbox => {
    checkbox.addEventListener('change', updateBatchButtonState);
});

// 全选/取消全选时同步状态
document.getElementById('headerSelectAll').addEventListener('change', function() {
    const isChecked = this.checked;
    rowCheckboxes.forEach(checkbox => checkbox.checked = isChecked);
    updateBatchButtonState();
});

用户体验优化

  • 按钮禁用状态通过disabled属性和opacity: 0.6样式双重提示。
  • 使用aria-disabled属性增强屏幕阅读器的可访问性。

四、安全防护与性能优化

1. 安全防护措施
(1)输入验证与过滤
  • 后端校验:使用@RequestParam(required = false)标记可选参数,避免null值注入。

  • 关键词过滤:对用户输入的keyword进行敏感词过滤(示例):

    if (StringUtils.hasText(keyword)) {
        keyword = sensitiveWordFilter.replace(keyword); // 替换敏感词
        queryWrapper.and(wrapper -> 
            wrapper.like(Comments::getNickname, keyword)
                  .or()
                  .like(Comments::getContent, keyword)
        );
    }
    
(2)XSS防护
  • Thymeleaf自动转义:默认开启HTML转义(${comment.content}会自动转义<>等字符)。

  • 手动转义:对于需要输出原始HTML的场景(如富文本编辑器内容),使用th:utext并配合自定义过滤器:

    // 自定义XSS过滤器(配置在WebMvcConfigurer中)
    @Bean
    public FilterRegistrationBean<XssFilter> xssFilter() {
        FilterRegistrationBean<XssFilter> registration = new FilterRegistrationBean<>();
        registration.setFilter(new XssFilter());
        registration.addUrlPatterns("/*"); // 拦截所有请求
        registration.setName("xssFilter");
        registration.setOrder(Ordered.HIGHEST_PRECEDENCE); // 最高优先级
        return registration;
    }
    
(3)CSRF防护
@PostMapping("/batchOperation")
public ResponseEntity<Void> batchOperation(
    @RequestParam("ids[]") List<Integer> ids,
    @RequestParam("operation") String operation,
    HttpServletRequest request) {

    // 验证CSRF Token(Spring Security自动处理)
    CsrfToken csrfToken = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
    if (csrfToken == null) {
        return ResponseEntity.status(HttpStatus.FORBIDDEN).build(); // 无Token拒绝访问
    }

    // 验证Token是否匹配(Spring Security自动完成,此处仅为示例)
    String requestToken = request.getHeader(csrfToken.getHeaderName());
    if (!csrfToken.getToken().equals(requestToken)) {
        return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
    }

    // ...业务逻辑
}
2. 性能优化策略
(1)数据库优化
  • 索引设计:在comments表创建联合索引(is_visible, create_time DESC),加速分页查询:

    CREATE INDEX idx_comments_status_time ON comments(is_visible, create_time DESC);
    
  • 慢查询监控:开启MySQL慢查询日志(slow_query_log=ON),定位执行时间超过1秒的SQL。

(2)前端性能优化
  • 虚拟滚动:对于超大数据量(如10万条评论),使用vue-virtual-scrollerreact-virtualized实现虚拟滚动,仅渲染可见区域的DOM节点。

  • 防抖搜索:对关键词输入框添加防抖(300ms),避免频繁触发搜索:

    let searchTimeout;
    document.getElementById('keywordFilter').addEventListener('input', (e) => {
        clearTimeout(searchTimeout);
        searchTimeout = setTimeout(() => {
            const form = e.target.closest('form');
            form.submit(); // 300ms无输入后提交表单
        }, 300);
    });
    

五、扩展功能与未来规划

1. 待实现功能
  • 审核工作流:增加多级审核(如编辑初审→管理员终审),通过状态机(0=待编辑初审1=待管理员终审2=已发布)管理流程。
  • 垃圾评论过滤:集成AI模型(如TensorFlow Lite)识别垃圾评论(广告、辱骂等),自动标记或屏蔽。
  • 实时通知:使用WebSocket实现新评论提醒(管理员收到WebSocket消息,前端弹出提示)。
2. 技术升级方向
  • 后端:迁移至Spring Boot 3.x,支持GraalVM原生编译,提升启动速度。
  • 前端:引入Vue 3或React,实现更复杂的交互(如拖拽排序、富文本编辑)。
  • 数据库:对于超大规模数据(如百万级评论),考虑分库分表(ShardingSphere)或使用列式数据库(ClickHouse)加速查询。

六、总结

本系统通过Spring Boot + MyBatis-Plus + Thymeleaf的技术组合,构建了一个高效、安全的博客评论管理系统。核心设计思想包括:

  • 动态SQL与分页优化:通过MyBatis-Plus的LambdaQueryWrapper简化条件构造,结合分页插件提升查询性能。
  • 响应式布局:基于媒体查询和Flex布局,实现PC与移动端的无缝切换。
  • 批量操作与事务安全:通过MyBatis-Plus的批量API和Spring的事务管理,保证数据一致性。
  • 多层安全防护:输入验证、XSS过滤、CSRF防护构建全方位安全体系。

未来可结合业务需求扩展审核流程、智能过滤等功能,同时关注新技术趋势(如Serverless、边缘计算)进一步优化系统性能。

如果你觉得文章对你有帮助,那就请作者喝杯咖啡吧☕
微信
支付宝
😀
😃
😄
😁
😆
😅
🤣
😂
🙂
🙃
😉
😊
😇
🥰
😍
🤩
😘
😗
☺️
😚
😙
🥲
😋
😛
😜
🤪
😝
🤑
🤗
🤭
🫢
🫣
🤫
🤔
🤨
😐
😑
😶
😏
😒
🙄
😬
😮‍💨
🤤
😪
😴
😷
🤒
🤕
🤢
🤮
🤧
🥵
🥶
🥴
😵
😵‍💫
🤯
🥳
🥺
😠
😡
🤬
🤯
😈
👿
💀
☠️
💩
👻
👽
👾
🤖
😺
😸
😹
😻
😼
😽
🙀
😿
😾
👋
🤚
🖐️
✋️
🖖
🫱
🫲
🫳
🫴
🫷
🫸
👌
🤌
🤏
✌️
🤞
🫰
🤟
🤘
🤙
👈️
👉️
👆️
🖕
👇️
☝️
🫵
👍️
👎️
✊️
👊
🤛
🤜
👏
🙌
👐
🤲
🤝
🙏
✍️
💅
🤳
💪
🦾
🦿
🦵
🦶
👂
🦻
👃
👶
👧
🧒
👦
👩
🧑
👨
👩‍🦱
👨‍🦱
👩‍🦰
👨‍🦰
👱‍♀️
👱‍♂️
👩‍🦳
👨‍🦳
👩‍🦲
👨‍🦲
🧔‍♀️
🧔‍♂️
👵
🧓
👴
👲
👳‍♀️
👳‍♂️
🧕
👮‍♀️
👮‍♂️
👷‍♀️
👷‍♂️
💂‍♀️
💂‍♂️
🕵️‍♀️
🕵️‍♂️
👩‍⚕️
👨‍⚕️
👩‍🌾
👨‍🌾
👩‍🍳
👨‍🍳
🐶
🐱
🐭
🐹
🐰
🦊
🐻
🐼
🐨
🐯
🦁
🐮
🐷
🐸
🐵
🐔
🐧
🐦
🦅
🦉
🐴
🦄
🐝
🪲
🐞
🦋
🐢
🐍
🦖
🦕
🐬
🦭
🐳
🐋
🦈
🐙
🦑
🦀
🦞
🦐
🐚
🐌
🐛
🦟
🪰
🪱
🦗
🕷️
🕸️
🦂
🦎
🐊
🐉
🐘
🦏
🦛
🐪
🐫
🦒
🦘
🦬
🐃
🐂
🐄
🐎
🐖
🐏
🐑
🐐
🦌
🐕
🐩
🦮
🐕‍🦺
🐈
🐈‍⬛
🐓
🦃
🦚
🦜
🦢
🦩
🕊️
🐇
🦝
🦨
🦡
🦫
🦦
🦥
🐁
🐀
🐿️
🦔
🌵
🎄
🌲
🌳
🌴
🌱
🌿
☘️
🍀
🎍
🎋
🍃
🍂
🍁
🍄
🌾
💐
🌷
🌹
🥀
🌺
🌸
🌼
🌻
🌞
🌝
🌛
🌜
🌚
🌕
🌖
🌗
🌘
🌑
🌒
🌓
🌔
🌙
🌎
🌍
🌏
🪐
💫
🌟
🔥
💥
☄️
☀️
🌤️
🌥️
🌦️
🌧️
⛈️
🌩️
🌨️
❄️
☃️
🌬️
💨
💧
💦
🌊
🍇
🍈
🍉
🍊
🍋
🍌
🍍
🥭
🍎
🍏
🍐
🍑
🍒
🍓
🥝
🍅
🥥
🥑
🍆
🥔
🥕
🌽
🌶️
🥒
🥬
🥦
🧄
🧅
🍄
🥜
🍞
🥐
🥖
🥨
🥯
🥞
🧇
🧀
🍖
🍗
🥩
🥓
🍔
🍟
🍕
🌭
🥪
🌮
🌯
🥙
🧆
🥚
🍳
🥘
🍲
🥣
🥗
🍿
🧈
🧂
🥫
🍱
🍘
🍙
🍚
🍛
🍜
🍝
🍠
🍢
🍣
🍤
🍥
🥮
🍡
🥟
🥠
🥡
🦪
🍦
🍧
🍨
🍩
🍪
🎂
🍰
🧁
🥧
🍫
🍬
🍭
🍮
🍯
🍼
🥛
🍵
🍶
🍾
🍷
🍸
🍹
🍺
🍻
🥂
🥃
🥤
🧃
🧉
🧊
🗺️
🏔️
⛰️
🌋
🏕️
🏖️
🏜️
🏝️
🏞️
🏟️
🏛️
🏗️
🏘️
🏙️
🏚️
🏠
🏡
🏢
🏣
🏤
🏥
🏦
🏨
🏩
🏪
🏫
🏬
🏭
🏯
🏰
💒
🗼
🗽
🕌
🛕
🕍
⛩️
🕋
🌁
🌃
🏙️
🌄
🌅
🌆
🌇
🌉
🎠
🎡
🎢
💈
🎪
🚂
🚃
🚄
🚅
🚆
🚇
🚈
🚉
🚊
🚝
🚞
🚋
🚌
🚍
🚎
🚐
🚑
🚒
🚓
🚔
🚕
🚖
🚗
🚘
🚙
🚚
🚛
🚜
🏎️
🏍️
🛵
🦽
🦼
🛺
🚲
🛴
🛹
🚏
🛣️
🛤️
🛢️
🚨
🚥
🚦
🚧
🛶
🚤
🛳️
⛴️
🛥️
🚢
✈️
🛩️
🛫
🛬
🪂
💺
🚁
🚟
🚠
🚡
🛰️
🚀
🛸
🧳
📱
💻
⌨️
🖥️
🖨️
🖱️
🖲️
💽
💾
📀
📼
🔍
🔎
💡
🔦
🏮
📔
📕
📖
📗
📘
📙
📚
📓
📒
📃
📜
📄
📰
🗞️
📑
🔖
🏷️
💰
💴
💵
💶
💷
💸
💳
🧾
✉️
📧
📨
📩
📤
📥
📦
📫
📪
📬
📭
📮
🗳️
✏️
✒️
🖋️
🖊️
🖌️
🖍️
📝
📁
📂
🗂️
📅
📆
🗒️
🗓️
📇
📈
📉
📊
📋
📌
📍
📎
🖇️
📏
📐
✂️
🗃️
🗄️
🗑️
🔒
🔓
🔏
🔐
🔑
🗝️
🔨
🪓
⛏️
⚒️
🛠️
🗡️
⚔️
🔫
🏹
🛡️
🔧
🔩
⚙️
🗜️
⚗️
🧪
🧫
🧬
🔬
🔭
📡
💉
🩸
💊
🩹
🩺
🚪
🛏️
🛋️
🪑
🚽
🚿
🛁
🧴
🧷
🧹
🧺
🧻
🧼
🧽
🧯
🛒
🚬
⚰️
⚱️
🗿
🏧
🚮
🚰
🚹
🚺
🚻
🚼
🚾
🛂
🛃
🛄
🛅
⚠️
🚸
🚫
🚳
🚭
🚯
🚱
🚷
📵
🔞
☢️
☣️
❤️
🧡
💛
💚
💙
💜
🖤
💔
❣️
💕
💞
💓
💗
💖
💘
💝
💟
☮️
✝️
☪️
🕉️
☸️
✡️
🔯
🕎
☯️
☦️
🛐
🆔
⚛️
🉑
☢️
☣️
📴
📳
🈶
🈚
🈸
🈺
🈷️
✴️
🆚
💮
🉐
㊙️
㊗️
🈴
🈵
🈹
🈲
🅰️
🅱️
🆎
🆑
🅾️
🆘
🛑
💢
💯
💠
♨️
🚷
🚯
🚳
🚱
🔞
📵
🚭
‼️
⁉️
🔅
🔆
🔱
⚜️
〽️
⚠️
🚸
🔰
♻️
🈯
💹
❇️
✳️
🌐
💠
Ⓜ️
🌀
💤
🏧
🚾
🅿️
🈳
🈂️
🛂
🛃
🛄
🛅
  6 条评论
大塘最帅boy   广东省深圳市

有点东西