V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
爱意满满的作品展示区。
smallbeef1998
V2EX  ›  分享创造

面向校招/社招,开源社区系统 Echo,基于目前主流 Java Web 技术栈,并提供详细的开发文档和配套教程

  •  4
     
  •   smallbeef1998 · 58 天前 · 2286 次点击
    这是一个创建于 58 天前的主题,其中的信息可能已经有所发展或是发生改变。

    博主目前东南大学研究生在读,近一个月开发了一款社区系统,为接下来的校招做准备,同时开源出来和各位小伙伴一起交流学习。项目仍然存在很多不完善的地方,大家多多指教~

    • 仓库地址https://github.com/Veal98/Echo

    • 在线体验:项目已经部署到腾讯云服务器,各位小伙伴们可直接线上体验:http://1.15.127.74/

      已内置三种不同身份的用户:

      username password 特殊权限
      管理员 admin admin 数据统计、删除帖子
      版主 master master 置顶帖子、加精帖子
      普通用户 user user
    • 文档地址:文档通过 Docsify + Gitee Pages 生成,在线访问地址: https://veal98.gitee.io/echo

    💻 核心技术栈

    后端:

    • Spring
    • Spring Boot 2.1.5 RELEASE
    • Spring MVC
    • ORM:MyBatis
    • 数据库:MySQL 5.7
    • 分布式缓存:Redis
    • 本地缓存:Caffeine
    • 消息队列:Kafka 2.13-2.7.0
    • 搜索引擎:Elasticsearch 6.4.3
    • 安全:Spring Security
    • 邮件任务:Spring Mail
    • 分布式定时任务:Spring Quartz
    • 日志:SLF4J (日志接口) + Logback (日志实现)

    前端:

    • Thymeleaf
    • Bootstrap 4.x
    • Jquery
    • Ajax

    🎀 界面展示

    首页:

    登录页:

    帖子详情页:

    个人主页:

    朋友私信页:

    私信详情页:

    系统通知页:

    通知详情页:

    账号设置页:

    数据统计页:

    搜索详情页:

    🎨 功能列表

    • 注册

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

      • 进入登录界面,动态生成验证码,并将验证码短暂存入 Redis ( 60 秒)
      • 用户登录成功(验证用户名、密码、验证码),生成登录凭证且设置状态为有效,并将登录凭证存入 Redis 注意:登录凭证存在有效期,在所有的请求执行之前,都会检查凭证是否有效和是否过期,只要该用户的凭证有效并在有效期时间内,本次请求就会一直持有该用户信息(使用 ThreadLocal 持有用户信息)
      • 勾选记住我,则延长登录凭证有效时间
      • 用户登录成功,将用户信息短暂存入 Redis ( 1 小时)
      • 用户登出,将凭证状态设为无效,并更新 Redis 中该用户的登录凭证信息
    • 账号设置

      • 修改头像
        • 将用户选择的头像图片文件上传至七牛云服务器
      • 修改密码
    • 帖子模块

      • 发布帖子(过滤敏感词),将其存入 MySQL
      • 分页显示所有的帖子
        • 支持按照 “发帖时间” 显示
        • 支持按照 “热度排行” 显示( Spring Quartz )
      • 查看帖子详情
      • 权限管理( Spring Security + Thymeleaf Security )
        • 未登录用户无法发帖
        • “版主” 可以看到帖子的置顶和加精按钮并执行相应操作
        • “管理员” 可以看到帖子的删除按钮并执行相应操作
        • “普通用户” 无法看到帖子的置顶、加精、删除按钮,也无法执行相应操作
    • 评论模块

      • 发布对帖子的评论(过滤敏感词),将其存入 MySQL
      • 分页显示评论
      • 发布对评论的回复(过滤敏感词)
      • 权限管理( Spring Security )
        • 未登录用户无法使用评论功能
    • 私信模块

      • 发送私信(过滤敏感词)
      • 私信列表
        • 查询当前用户的会话列表
        • 每个会话只显示一条最新的私信
        • 支持分页显示
      • 私信详情
        • 查询某个会话所包含的所有私信
        • 访问私信详情时,将显示的私信设为已读状态
        • 支持分页显示
      • 权限管理( Spring Security )
        • 未登录用户无法使用私信功能
    • 统一处理 404 / 500 异常

      • 普通请求异常
      • 异步请求异常
    • 统一记录日志

    • 点赞模块

      • 支持对帖子、评论 /回复点赞
      • 第 1 次点赞,第 2 次取消点赞
      • 首页统计帖子的点赞数量
      • 详情页统计帖子和评论 /回复的点赞数量
      • 详情页显示当前登录用户的点赞状态(赞过了则显示已赞)
      • 统计我的获赞数量
      • 权限管理( Spring Security )
        • 未登录用户无法使用点赞相关功能
    • 关注模块

      • 关注功能
      • 取消关注功能
      • 统计用户的关注数和粉丝数
      • 我的关注列表(查询某个用户关注的人),支持分页
      • 我的粉丝列表(查询某个用户的粉丝),支持分页
      • 权限管理( Spring Security )
        • 未登录用户无法使用关注相关功能
    • 系统通知模块

      • 通知列表
        • 显示评论、点赞、关注三种类型的通知
      • 通知详情
        • 分页显示某一类主题所包含的通知
        • 进入某种类型的系统通知详情,则将该页的所有未读的系统通知状态设置为已读
      • 未读数量
        • 分别显示每种类型的系统通知的未读数量
        • 显示所有系统通知的未读数量
      • 导航栏显示所有消息的未读数量(未读私信 + 未读系统通知)
      • 权限管理( Spring Security )
        • 未登录用户无法使用系统通知功能
    • 搜索模块

      • 发布事件
        • 发布帖子时,通过消息队列将帖子异步地提交到 Elasticsearch 服务器
        • 为帖子增加评论时,通过消息队列将帖子异步地提交到 Elasticsearch 服务器
      • 搜索服务
        • 从 Elasticsearch 服务器搜索帖子
        • 从 Elasticsearch 服务器删除帖子(当帖子从数据库中被删除时)
      • 显示搜索结果
    • 网站数据统计(管理员专属)

      • 独立访客 UV
        • 存入 Redis 的 HyperLogLog
        • 支持单日查询和区间日期查询
      • 日活跃用户 DAU
        • 存入 Redis 的 Bitmap
        • 支持单日查询和区间日期查询
      • 权限管理( Spring Security )
        • 只有管理员可以查看网站数据统计
    • 优化网站性能

      • 使用本地缓存 Caffeine 缓存热帖列表以及所有用户帖子的总数

    🔐 待实现及优化

    以下是我个人发现的本项目存在的问题,但是暂时没有头绪无法解决,集思广益,欢迎各位小伙伴提 PR 解决:

    • 注册模块无法正常跳转到操作提示界面(本地运行没有问题)
    • 评论功能的前端显示部分存在 Bug
    • 查询我的评论(未完善)

    以下是我觉得本项目还可以添加的功能,同样欢迎各位小伙伴提 issue 指出还可以增加哪些功能,或者直接提 PR 实现该功能:

    • 忘记密码(发送邮件找回密码)
    • 查询我的点赞
    • 管理员对帖子的二次点击取消置顶功能
    • 管理员对已删除帖子的恢复功能(本项目中的删除帖子并未将其从数据库中删除,只是将其状态设置为了拉黑)

    🌱 本地运行

    各位如果需要将项目部署在本地进行测试,以下环境请提前备好:

    • Java 8
    • MySQL 5.7
    • Redis
    • Kafka 2.13-2.7.0
    • Elasticsearch 6.4.3

    然后修改配置文件中的信息为你自己的本地环境,直接运行是运行不了的,而且相关私密信息我全部用 xxxxxxx 代替了。

    本地运行需要修改的配置文件信息如下:

    1 )application-develop.properties

    • MySQL
    • Spring Mail (邮箱需要开启 SMTP 服务)
    • Kafka:consumer.group-id (该字段见 Kafka 安装包中的 consumer.proerties,可自行修改, 修改完毕后需要重启 Kafka )
    • Elasticsearch:cluster-name (该字段见 Elasticsearch 安装包中的 elasticsearch.yml ,可自行修改)
    • 七牛云(需要新建一个七牛云的对象存储空间,用来存放上传的头像图片)

    2 )logback-spring-develop.xml

    • LOG_PATH:日志存放的位置

    每次运行需要打开:

    • MySQL
    • Redis
    • Elasticsearch
    • Kafka

    另外,还需要事件建好数据库表,详细见下文。

    📜 数据库设计

    用户 user

    DROP TABLE IF EXISTS `user`;
    SET character_set_client = utf8mb4 ;
    CREATE TABLE `user` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `username` varchar(50) DEFAULT NULL,
      `password` varchar(50) DEFAULT NULL,
      `salt` varchar(50) DEFAULT NULL,
      `email` varchar(100) DEFAULT NULL,
      `type` int(11) DEFAULT NULL COMMENT '0-普通用户; 1-超级管理员; 2-版主;',
      `status` int(11) DEFAULT NULL COMMENT '0-未激活; 1-已激活;',
      `activation_code` varchar(100) DEFAULT NULL,
      `header_url` varchar(200) DEFAULT NULL,
      `create_time` timestamp NULL DEFAULT NULL,
      PRIMARY KEY (`id`),
      KEY `index_username` (`username`(20)),
      KEY `index_email` (`email`(20))
    ) ENGINE=InnoDB AUTO_INCREMENT=101 DEFAULT CHARSET=utf8;
    

    讨论帖 discuss_post

    DROP TABLE IF EXISTS `discuss_post`;
    SET character_set_client = utf8mb4 ;
    CREATE TABLE `discuss_post` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `user_id` int(11) DEFAULT NULL,
      `title` varchar(100) DEFAULT NULL,
      `content` text,
      `type` int(11) DEFAULT NULL COMMENT '0-普通; 1-置顶;',
      `status` int(11) DEFAULT NULL COMMENT '0-正常; 1-精华; 2-拉黑;',
      `create_time` timestamp NULL DEFAULT NULL,
      `comment_count` int(11) DEFAULT NULL,
      `score` double DEFAULT NULL,
      PRIMARY KEY (`id`),
      KEY `index_user_id` (`user_id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    

    评论(回复)comment

    CREATE TABLE `comment` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `user_id` int(11) DEFAULT NULL,
      `entity_type` int(11) DEFAULT NULL COMMENT '评论目标的类别:1 帖子; 2 评论 ',
      `entity_id` int(11) DEFAULT NULL COMMENT '评论目标的 id',
      `target_id` int(11) DEFAULT NULL COMMENT '指明对谁进行评论',
      `content` text,
      `status` int(11) DEFAULT NULL COMMENT '状态:0 正常; 1 禁用',
      `create_time` timestamp NULL DEFAULT NULL,
      PRIMARY KEY (`id`),
      KEY `index_user_id` (`user_id`),
      KEY `index_entity_id` (`entity_id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=247 DEFAULT CHARSET=utf8;
    

    私信 message

    DROP TABLE IF EXISTS `message`;
    SET character_set_client = utf8mb4 ;
    CREATE TABLE `message` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `from_id` int(11) DEFAULT NULL,
      `to_id` int(11) DEFAULT NULL,
      `conversation_id` varchar(45) NOT NULL,
      `content` text,
      `status` int(11) DEFAULT NULL COMMENT '0-未读;1-已读;2-删除;',
      `create_time` timestamp NULL DEFAULT NULL,
      PRIMARY KEY (`id`),
      KEY `index_from_id` (`from_id`),
      KEY `index_to_id` (`to_id`),
      KEY `index_conversation_id` (`conversation_id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    

    🌌 理想的部署架构

    我每个都只部署了一台,以下是理想的部署架构:

    🎯 功能逻辑图

    画了一些不是那么严谨的图帮助各位小伙伴理清思绪。

    单向绿色箭头:

    • 前端模板 -> Controller:表示这个前端模板中有一个超链接是由这个 Controller 处理的
    • Controller -> 前端模板:表示这个 Controller 会像该前端模板传递数据或者跳转

    双向绿色箭头:表示 Controller 和前端模板之间进行参数的相互传递或使用

    单向蓝色箭头:A -> B,表示 A 方法调用了 B 方法

    单向红色箭头:数据库或缓存操作

    注册

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

    登录 | 登出

    • 进入登录界面,动态生成验证码,并将验证码短暂存入 Redis ( 60 秒)

    • 用户登录成功(验证用户名、密码、验证码),生成登录凭证且设置状态为有效,并将登录凭证存入 Redis

      注意:登录凭证存在有效期,在所有的请求执行之前,都会检查凭证是否有效和是否过期,只要该用户的凭证有效并在有效期时间内,本次请求就会一直持有该用户信息(使用 ThreadLocal 持有用户信息)

    • 勾选记住我,则延长登录凭证有效时间

    • 用户登录成功,将用户信息短暂存入 Redis ( 1 小时)

    • 用户登出,将凭证状态设为无效,并更新 Redis 中该用户的登录凭证信息

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

    分页显示所有的帖子

    • 支持按照 “发帖时间” 显示
    • 支持按照 “热度排行” 显示( Spring Quartz )
    • 将热帖列表和所有帖子的总数存入本地缓存 Caffeine (利用分布式定时任务 Spring Quartz 每隔一段时间就刷新计算帖子的热度 /分数 — 见下文,而 Caffeine 里的数据更新不用我们操心,它天生就会自动的更新它拥有的数据,给它一个初始化方法就完事儿)

    账号设置

    • 修改头像(异步请求)
      • 将用户选择的头像图片文件上传至七牛云服务器
    • 修改密码

    此处只画出修改头像:

    发布帖子(异步请求)

    显示评论及相关信息

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

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

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

    一个帖子的详情页需要封装的信息大概如下:

    添加评论(事务管理)

    私信列表和详情页

    发送私信(异步请求)

    点赞(异步请求)

    将点赞相关信息存入 Redis 的数据结构 set 中。其中,key 命名为 like:entity:entityType:entityId,value 即点赞用户的 id 。比如 key = like:entity:2:246 value = 11 表示用户 11 对实体类型 2 即评论进行了点赞,该评论的 id 是 246

    某个用户的获赞数量对应的存储在 Redis 中的 key 是 like:user:userId,value 就是这个用户的获赞数量

    我的获赞数量

    关注(异步请求)

    • 若 A 关注了 B,则 A 是 B 的粉丝 Follower,B 是 A 的目标 Followee
    • 关注的目标可以是用户、帖子、题目等,在实现时将这些目标抽象为实体(目前只做了关注用户)

    将某个用户关注的实体相关信息存储在 Redis 的数据结构 zset 中:key 是 followee:userId:entityType ,对应的 value 是 zset(entityId, now) ,以关注的时间进行排序。比如说 followee:111:3 对应的 value (20, 2020-02-03-xxxx),表明用户 111 关注了实体类型为 3 即人(用户),该帖子的 id 是 20,关注该帖子的时间是 2020-02-03-xxxx

    同样的,将某个实体拥有的粉丝相关信息也存储在 Redis 的数据结构 zset 中:key 是 follower:entityType:entityId,对应的 value 是 zset(userId, now),以关注的时间进行排序

    关注列表

    发送系统通知

    显示系统通知

    搜索

    类似的,置顶、加精也会触发发帖事件,就不再图里面画出来了。

    置顶加精删除(异步请求)

    网站数据统计

    帖子热度计算

    每次发生点赞(给帖子点赞)、评论(给帖子评论)、加精的时候,就将这些帖子信息存入缓存 Redis 中,然后通过分布式的定时任务 Spring Quartz,每隔一段时间就从缓存中取出这些帖子进行计算分数。

    帖子分数 /热度计算公式:分数(热度) = 权重 + 发帖距离天数

    // 计算权重
    double w = (wonderful ? 75 : 0) + commentCount * 10 + likeCount * 2;
    // 分数 = 权重 + 发帖距离天数
    double score = Math.log10(Math.max(w, 1))
            + (post.getCreateTime().getTime() - epoch.getTime()) / (1000 * 3600 * 24);
    

    📖 配套教程

    想要自己从零开始实现这个项目或者深入理解的小伙伴,可以扫描下方二维码关注公众号『飞天小牛肉』,第一时间获取配套教程, 不仅会详细解释本项目涉及的各大技术点,还会汇总相关的常见面试题,目前尚在更新中。

    并推荐我的开源教程类项目 『 CS-Wiki 』,Gitee 推荐项目,目前已 0.9k star: 致力打造完善的 Java 后端知识体系,不仅仅帮助各位小伙伴快速且系统的准备面试(秋招 /社招),更指引学习的方向

    📞 联系我

    有什么问题也可以添加我的微信,记得备注来意:格式 <u>(学校或公司 - 姓名或昵称 - 来意)</u>

    👏 鸣谢

    本项目参考牛客网 — Java 高级工程师课程,感谢老师和平台

    46 条回复    2021-02-17 00:21:00 +08:00
    stabc
        1
    stabc   58 天前
    你这是招聘贴么?
    coolyan
        2
    coolyan   58 天前
    请问你的图是用什么软件画的?
    smallbeef1998
        3
    smallbeef1998   58 天前
    @stabc 不是 哈哈哈哈,标题好像有歧义,是我自己准备秋招做的项目🤣
    smallbeef1998
        4
    smallbeef1998   58 天前
    @coolyan draw.io 在线画图网站
    smallbeef1998
        5
    smallbeef1998   58 天前
    @smallbeef1998 改成 备战秋招 /校招,哈哈哈哈
    NIYIKI
        6
    NIYIKI   58 天前
    不错,已 star
    smallbeef1998
        7
    smallbeef1998   58 天前
    @NIYIKI 😁 感谢,后续会不断完善
    selendipity
        8
    selendipity   58 天前
    做成这样一个项目可以进什么公司?
    smallbeef1998
        9
    smallbeef1998   58 天前 via iPhone
    @selendipity 结果并不重要吧 重要的是弄懂项目包含的技术点
    alexkuang
        10
    alexkuang   58 天前
    谢谢大佬,功能逻辑图很详细,正好最近学校做个项目包含社区系统,参考一下
    Mirage09
        11
    Mirage09   58 天前 via iPhone
    Why Kafka?
    smallbeef1998
        12
    smallbeef1998   58 天前 via iPhone
    @alexkuang 不是大佬啦,点个 star 就是最大的支持😁
    smallbeef1998
        13
    smallbeef1998   58 天前 via iPhone
    @Mirage09 高吞吐量、分布式架构、发布订阅模型
    learningman
        14
    learningman   58 天前 via Android   ❤️ 1
    就挺离谱的,都写了这么多页面了,在多写一个 install 也没多麻烦啊。。。
    每次看到这种要手动导入 SQL 的玩意儿就觉得一股业余的味
    smallbeef1998
        15
    smallbeef1998   58 天前 via iPhone
    @learningman 大佬 我确实经验不足😂
    smallbeef1998
        16
    smallbeef1998   58 天前 via iPhone
    @learningman 不知道您说的 install 是个啥意思😅
    JustSong
        17
    JustSong   58 天前 via Android
    @smallbeef1998 应该就是一个安装配置页面,类似 WordPress 的初始配置页面
    smallbeef1998
        18
    smallbeef1998   58 天前
    @JustSong 这不太好整吧,毕竟大家本地环境不一样😂
    JustSong
        19
    JustSong   58 天前 via Android
    @smallbeef1998 配置下数据库而已呀,本地环境有啥影响
    Cbdy
        20
    Cbdy   58 天前 via Android
    蛮怪的
    smallbeef1998
        21
    smallbeef1998   58 天前
    @Cbdy 大佬,怎么说😄
    smallbeef1998
        22
    smallbeef1998   58 天前 via iPhone
    为什么大家都在收藏却不愿意给个 star😂
    wbd31
        23
    wbd31   58 天前
    项目的 kafka 是用在了什么地方
    smallbeef1998
        24
    smallbeef1998   58 天前 via iPhone
    @wbd31 关注、点赞、评论的系统通知,以及相应的触发 Elasticsearch 数据的更新。可以看看逻辑图😀
    anrgct
        25
    anrgct   58 天前 via iPhone
    冲这么多字支持一下😬
    smallbeef1998
        26
    smallbeef1998   58 天前 via iPhone
    @anrgct 🥳
    lookcos
        27
    lookcos   58 天前
    这种项目适合学习与教学,能说明轮子玩的够熟。
    lookcos
        28
    lookcos   58 天前
    我的建议是:
    1. console 提的报错与警告修改一下
    ```JavaScript
    Uncaught SyntaxError: Unexpected token 'export'
    DevTools failed to load SourceMap: Could not load content for http://1.15.127.74/js/bootstrap.min.js.map: HTTP error: status code 404, net::ERR_HTTP_RESPONSE_CODE_FAILURE
    ```
    2.如果有余力,可以把网站修改为,首屏 SSR,其余的使用 restful API.
    好处在于,1. 增加了前后端分离的模式 2.提升了用户体验 3.减轻了服务器后端的压力(尤其高并发场景,能节约带宽与算力)
    smallbeef1998
        29
    smallbeef1998   58 天前 via iPhone
    @lookcos 感谢大佬建议啊😄 不过对我这样的初学者来说,前后端不分离相对来说比较友好,强行前后端分离容易把我搞懵
    lasfresas
        30
    lasfresas   58 天前
    感觉做的挺好
    smallbeef1998
        31
    smallbeef1998   58 天前 via iPhone
    @lasfresas 感谢😁
    wolfan
        32
    wolfan   58 天前 via Android
    移动端适配不成。
    chenlee9876
        33
    chenlee9876   57 天前 via iPhone
    老哥你直接把 IP 暴露出来真的大丈夫嘛?不考虑整个域名之类的嘛?
    smallbeef1998
        34
    smallbeef1998   57 天前 via iPhone
    @chenlee9876 不考虑😂 练手项目 没必要吧
    919615766
        35
    919615766   57 天前 via iPhone
    up 研一已经学了这么多东西了吗 本科也是 cs ?
    smallbeef1998
        36
    smallbeef1998   57 天前
    @919615766 对的
    Adaocean
        37
    Adaocean   57 天前 via Android
    @smallbeef1998 前辈的作品完整度也太高了。
    smallbeef1998
        38
    smallbeef1998   57 天前 via iPhone
    @Adaocean 哈哈哈哈 不然怎么在疯狂的内卷里活下来
    smallbeef1998
        39
    smallbeef1998   57 天前 via iPhone
    @wolfan 是这样的 我一个人精力实在有限 开源出来也是期待诸位大佬们帮助完善 让我跟在后面学习学习😀
    Adaocean
        40
    Adaocean   57 天前 via Android
    @smallbeef1998 很棒 赞赞赞
    hello2060
        41
    hello2060   57 天前 via iPhone
    4 系老校友只能帮顶了
    smallbeef1998
        42
    smallbeef1998   57 天前 via iPhone
    @hello2060 哈哈哈哈哈 多谢学长
    wssy
        43
    wssy   55 天前 via Android
    图画的蛮好
    smallbeef1998
        44
    smallbeef1998   55 天前 via iPhone
    @wssy 多谢🍭
    golangLover
        45
    golangLover   54 天前
    大神厉害了
    smallbeef1998
        46
    smallbeef1998   54 天前 via iPhone
    @golangLover 不是大神😂 共同进步
    关于   ·   帮助文档   ·   FAQ   ·   API   ·   我们的愿景   ·   广告投放   ·   感谢   ·   实用小工具   ·   2445 人在线   最高记录 5497   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 21ms · UTC 12:43 · PVG 20:43 · LAX 05:43 · JFK 08:43
    ♥ Do have faith in what you're doing.