├── .gitattributes
├── src
└── main
│ ├── resources
│ ├── application.yml
│ ├── static
│ │ ├── css
│ │ │ ├── login.css
│ │ │ ├── letter.css
│ │ │ ├── discuss-detail.css
│ │ │ └── global.css
│ │ ├── img
│ │ │ ├── 404.png
│ │ │ ├── error.png
│ │ │ └── icon.png
│ │ └── js
│ │ │ ├── register.js
│ │ │ ├── discuss.js
│ │ │ ├── profile.js
│ │ │ ├── global.js
│ │ │ ├── index.js
│ │ │ ├── letter.js
│ │ │ └── bs-custom-file-input.js
│ ├── templates
│ │ ├── mail
│ │ │ ├── forget.html
│ │ │ └── activation.html
│ │ ├── error
│ │ │ ├── 404.html
│ │ │ └── 500.html
│ │ └── site
│ │ │ ├── operate-result.html
│ │ │ ├── my-post.html
│ │ │ ├── my-comment.html
│ │ │ ├── profile.html
│ │ │ ├── followee.html
│ │ │ ├── follower.html
│ │ │ ├── register.html
│ │ │ ├── login.html
│ │ │ ├── setting.html
│ │ │ ├── letter-detail.html
│ │ │ ├── letter.html
│ │ │ ├── admin
│ │ │ └── data.html
│ │ │ ├── forget.html
│ │ │ └── notice.html
│ ├── application-pro.yml
│ ├── application-dev.yml
│ └── mapper
│ │ ├── UserMapper.xml
│ │ ├── CommentMapper.xml
│ │ ├── DiscussPostMapper.xml
│ │ └── MessageMapper.xml
│ └── java
│ └── com
│ └── community
│ ├── entity
│ ├── LoginTicket.java
│ ├── Message.java
│ ├── Comment.java
│ ├── DiscussPost.java
│ ├── User.java
│ └── Page.java
│ ├── CommunityApplication.java
│ ├── annotation
│ └── LoginRequired.java
│ ├── service
│ ├── LikeService.java
│ ├── CommentService.java
│ ├── FollowService.java
│ ├── MessageService.java
│ ├── DiscussPostService.java
│ ├── UserService.java
│ └── impl
│ │ ├── MessageServiceImpl.java
│ │ ├── DiscussPostServiceImpl.java
│ │ ├── LikeServiceImpl.java
│ │ ├── CommentServiceImpl.java
│ │ ├── FollowServiceImpl.java
│ │ └── UserServiceImpl.java
│ ├── utils
│ ├── UserThreadLocal.java
│ ├── CommonUtil.java
│ ├── CookieUtil.java
│ ├── Constant.java
│ ├── MailClient.java
│ └── RedisKeyUtil.java
│ ├── vo
│ └── ResultVo.java
│ ├── mapper
│ ├── UserMapper.java
│ ├── CommentMapper.java
│ ├── MessageMapper.java
│ └── DiscussPostMapper.java
│ ├── config
│ ├── RedisConfig.java
│ ├── KaptchaConfig.java
│ └── WebMvcConfig.java
│ ├── controller
│ ├── CommentController.java
│ ├── advice
│ │ └── ExceptionAdvice.java
│ ├── LikeController.java
│ ├── HomeController.java
│ ├── FollowController.java
│ ├── MessageController.java
│ ├── LoginController.java
│ ├── DiscussPostController.java
│ └── UserController.java
│ └── interceptor
│ ├── LoginRequiredInterceptor.java
│ └── LoginTicketInterceptor.java
├── .gitignore
├── README.md
├── community.sql
└── pom.xml
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.html linguist-language=java
--------------------------------------------------------------------------------
/src/main/resources/application.yml:
--------------------------------------------------------------------------------
1 | spring:
2 | profiles:
3 | active: dev
--------------------------------------------------------------------------------
/src/main/resources/static/css/login.css:
--------------------------------------------------------------------------------
1 | .main .container {
2 | width: 720px;
3 | }
4 |
--------------------------------------------------------------------------------
/src/main/resources/static/img/404.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/weizujie/community/HEAD/src/main/resources/static/img/404.png
--------------------------------------------------------------------------------
/src/main/resources/static/img/error.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/weizujie/community/HEAD/src/main/resources/static/img/error.png
--------------------------------------------------------------------------------
/src/main/resources/static/img/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/weizujie/community/HEAD/src/main/resources/static/img/icon.png
--------------------------------------------------------------------------------
/src/main/resources/static/js/register.js:
--------------------------------------------------------------------------------
1 | $(function () {
2 | $("input").focus(clear_error);
3 | });
4 |
5 | function clear_error() {
6 | $(this).removeClass("is-invalid");
7 | }
--------------------------------------------------------------------------------
/src/main/resources/static/css/letter.css:
--------------------------------------------------------------------------------
1 | .main .nav .badge {
2 | position: absolute;
3 | top: -3px;
4 | left: 68px;
5 | }
6 |
7 | .main .media .badge {
8 | position: absolute;
9 | top: 12px;
10 | left: -3px;
11 | }
12 |
13 | .toast {
14 | max-width: 100%;
15 | width: 80%;
16 | }
--------------------------------------------------------------------------------
/src/main/resources/static/css/discuss-detail.css:
--------------------------------------------------------------------------------
1 | .content {
2 | font-size: 16px;
3 | line-height: 2em;
4 | }
5 |
6 | .replyform textarea {
7 | width: 100%;
8 | height: 200px;
9 | }
10 |
11 | .floor {
12 | background: #dcdadc;
13 | padding: 4px 12px;
14 | border-radius: 3px;
15 | font-size: 14px;
16 | }
17 |
18 | .input-size {
19 | width: 100%;
20 | height: 35px;
21 | }
--------------------------------------------------------------------------------
/src/main/java/com/community/entity/LoginTicket.java:
--------------------------------------------------------------------------------
1 | package com.community.entity;
2 |
3 | import lombok.Data;
4 |
5 | import java.util.Date;
6 |
7 | /**
8 | * 登录凭证(后面会改成 Redis)
9 | */
10 | @Data
11 | public class LoginTicket {
12 |
13 |
14 | private Integer id;
15 |
16 | private Integer userId;
17 |
18 | private String ticket;
19 |
20 | private Integer status;
21 |
22 | private Date expired;
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/src/main/resources/templates/mail/forget.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | 忘记密码
7 |
8 |
9 |
10 |
11 | xxx@xxx.com, 您好!
12 |
13 |
14 | 您正在找回牛客账号的密码, 本次操作的验证码为 u5s6dt ,
15 | 有效时间5分钟, 请您及时进行操作!
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/src/main/java/com/community/CommunityApplication.java:
--------------------------------------------------------------------------------
1 | package com.community;
2 |
3 | import org.mybatis.spring.annotation.MapperScan;
4 | import org.springframework.boot.SpringApplication;
5 | import org.springframework.boot.autoconfigure.SpringBootApplication;
6 |
7 | @SpringBootApplication
8 | @MapperScan("com.community.mapper")
9 | public class CommunityApplication {
10 |
11 | public static void main(String[] args) {
12 | SpringApplication.run(CommunityApplication.class, args);
13 | }
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/src/main/resources/templates/mail/activation.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | 激活账号
7 |
8 |
9 |
10 |
11 | xxx@xxx.com, 您好!
12 |
13 |
14 | 您正在注册 Community, 这是一封激活邮件, 请点击
15 | 此链接,
16 | 激活您的 Community 账号!
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/src/main/java/com/community/annotation/LoginRequired.java:
--------------------------------------------------------------------------------
1 | package com.community.annotation;
2 |
3 |
4 | import java.lang.annotation.ElementType;
5 | import java.lang.annotation.Retention;
6 | import java.lang.annotation.RetentionPolicy;
7 | import java.lang.annotation.Target;
8 |
9 | /**
10 | * 自定义注解,用来描述哪些方法被拦截器拦截(需要配置拦截器)
11 | * Target(ElementType.METHOD) -> 用来描述方法
12 | * Retention(RetentionPolicy.RUNTIME) -> 声明该注解有效的时长(程序运行的时候有效)
13 | */
14 | @Target(ElementType.METHOD)
15 | @Retention(RetentionPolicy.RUNTIME)
16 | public @interface LoginRequired {
17 | }
18 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | HELP.md
2 | src/main/resources/application-pro.yml
3 | target/
4 | !.mvn/wrapper/maven-wrapper.jar
5 | !**/src/main/**/target/
6 | !**/src/test/**/target/
7 |
8 | ### STS ###
9 | .apt_generated
10 | .classpath
11 | .factorypath
12 | .project
13 | .settings
14 | .springBeans
15 | .sts4-cache
16 |
17 | ### IntelliJ IDEA ###
18 | .idea
19 | *.iws
20 | *.iml
21 | *.ipr
22 |
23 | ### NetBeans ###
24 | /nbproject/private/
25 | /nbbuild/
26 | /dist/
27 | /nbdist/
28 | /.nb-gradle/
29 | build/
30 | !**/src/main/**/build/
31 | !**/src/test/**/build/
32 |
33 | ### VS Code ###
34 | .vscode/
--------------------------------------------------------------------------------
/src/main/resources/static/js/discuss.js:
--------------------------------------------------------------------------------
1 |
2 | function like(btn, entityType, entityId, entityUserId) {
3 | $.post(
4 | "/like",
5 | {"entityType": entityType, "entityId": entityId, "entityUserId": entityUserId},
6 | function (data) {
7 | data = $.parseJSON(data);
8 | if (data.code === 0) {
9 | $(btn).children("i").text(data.likeCount);
10 | $(btn).children("b").text(data.likeStatus === 1 ? '已赞' : '赞');
11 | } else {
12 | alert(data.msg);
13 | }
14 | }
15 | );
16 | }
--------------------------------------------------------------------------------
/src/main/java/com/community/service/LikeService.java:
--------------------------------------------------------------------------------
1 | package com.community.service;
2 |
3 | public interface LikeService {
4 |
5 | /**
6 | * 点赞
7 | */
8 | void like(int userId, int entityType, int entityId, int entityUserId);
9 |
10 | /**
11 | * 查询某实体点赞的数量
12 | */
13 | long selectEntityLikeCount(int entityType, int entityId);
14 |
15 | /**
16 | * 查询某用户对某实体点赞的状态
17 | */
18 | int selectEntityLikeStatus(int userId, int entityType, int entityId);
19 |
20 | /**
21 | * 查询某个用户获得的赞
22 | */
23 | int selectUserLikeCount(int userId);
24 | }
25 |
--------------------------------------------------------------------------------
/src/main/java/com/community/utils/UserThreadLocal.java:
--------------------------------------------------------------------------------
1 | package com.community.utils;
2 |
3 | import com.community.entity.User;
4 | import org.springframework.stereotype.Component;
5 |
6 | /**
7 | * 持有用户的信息,用于代替 Session 对象
8 | */
9 | @Component
10 | public class UserThreadLocal {
11 |
12 | private ThreadLocal users = new ThreadLocal<>();
13 |
14 | public void setUsers(User user) {
15 | users.set(user);
16 | }
17 |
18 | public User getUser() {
19 | return users.get();
20 | }
21 |
22 | public void clear() {
23 | users.remove();
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/src/main/java/com/community/utils/CommonUtil.java:
--------------------------------------------------------------------------------
1 | package com.community.utils;
2 |
3 |
4 | import org.apache.commons.lang3.StringUtils;
5 | import org.springframework.util.DigestUtils;
6 |
7 | import java.util.UUID;
8 |
9 | public class CommonUtil {
10 |
11 | // 生成随机字符串
12 | public static String generateUUID() {
13 | return UUID.randomUUID().toString().replaceAll("-", "");
14 | }
15 |
16 | // MD5 + salt 加密
17 | public static String md5(String key) {
18 | // 字符串为空(包括空格)就不加密
19 | if (StringUtils.isBlank(key)) {
20 | return null;
21 | }
22 | return DigestUtils.md5DigestAsHex(key.getBytes());
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/main/java/com/community/entity/Message.java:
--------------------------------------------------------------------------------
1 | package com.community.entity;
2 |
3 | import com.baomidou.mybatisplus.annotation.TableName;
4 | import lombok.Data;
5 |
6 | import java.util.Date;
7 |
8 | @Data
9 | @TableName("message")
10 | public class Message {
11 |
12 | private Integer id;
13 | // 消息发送用户 id( fromId为1表示系统用户,发送的不是私信而是通知)
14 | private Integer fromId;
15 | // 消息接收用户 id
16 | private Integer toId;
17 | // 会话 id(冗余字段,为了方便查询)
18 | private String conversationId;
19 | // 内容
20 | private String content;
21 | // 状态 0-未读 1-已读 2-删除
22 | private Integer status;
23 | // 创建时间
24 | private Date createTime;
25 |
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/src/main/java/com/community/entity/Comment.java:
--------------------------------------------------------------------------------
1 | package com.community.entity;
2 |
3 | import com.baomidou.mybatisplus.annotation.TableName;
4 | import lombok.Data;
5 |
6 | import java.util.Date;
7 |
8 | @Data
9 | @TableName("comment")
10 | public class Comment {
11 |
12 | // 评论 id
13 | private Integer id;
14 | // 用户 id
15 | private Integer userId;
16 | // 实体类型 0-评论 1-回复(给评论的评论)
17 | private Integer entityType;
18 | // 实体 id
19 | private Integer entityId;
20 | // 回复或评论的目标用户
21 | private Integer targetId;
22 | // 正文
23 | private String content;
24 | // 状态 0-正常
25 | private Integer status;
26 | // 创建时间
27 | private Date createTime;
28 |
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/src/main/java/com/community/entity/DiscussPost.java:
--------------------------------------------------------------------------------
1 | package com.community.entity;
2 |
3 | import com.baomidou.mybatisplus.annotation.TableName;
4 | import lombok.Data;
5 |
6 | import java.util.Date;
7 |
8 |
9 | @Data
10 | @TableName("discuss_post")
11 | public class DiscussPost {
12 |
13 | // 帖子 id
14 | private Integer id;
15 | // 用户 id
16 | private Integer userId;
17 | // 标题
18 | private String title;
19 | // 内容
20 | private String content;
21 | // 分类 0-普通 1-置顶
22 | private Integer type;
23 | // 状态 0-正常 1-精华 2-拉黑
24 | private Integer status;
25 | // 评论数(冗余的写在这里,正确做法应该是在 comment 表里,但效率低)
26 | private Integer commentCount;
27 | // 分数 用于计算热贴排行
28 | private Double score;
29 | // 创建时间
30 | private Date createTime;
31 | }
32 |
--------------------------------------------------------------------------------
/src/main/java/com/community/entity/User.java:
--------------------------------------------------------------------------------
1 | package com.community.entity;
2 |
3 | import com.baomidou.mybatisplus.annotation.TableName;
4 | import lombok.Data;
5 |
6 | import java.util.Date;
7 |
8 | @Data
9 | @TableName("user")
10 | public class User {
11 |
12 | // 用户 id
13 | private Integer id;
14 | // 用户名
15 | private String username;
16 | // 密码
17 | private String password;
18 | // 加密盐
19 | private String salt;
20 | // 邮箱
21 | private String email;
22 | // 用户类别 0-普通用户 1-超级管理员 2-版主
23 | private Integer type;
24 | // 状态 0-未激活 1-已激活
25 | private Integer status;
26 | // 激活码
27 | private String activationCode;
28 | // 头像
29 | private String headerUrl;
30 | // 创建时间
31 | private Date createTime;
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/src/main/java/com/community/utils/CookieUtil.java:
--------------------------------------------------------------------------------
1 | package com.community.utils;
2 |
3 | import javax.servlet.http.Cookie;
4 | import javax.servlet.http.HttpServletRequest;
5 |
6 | /**
7 | * Cookie 工具类
8 | * 用来获取 cookie 里的值
9 | */
10 | public class CookieUtil {
11 |
12 | public static String getValue(HttpServletRequest request, String name) {
13 |
14 | if (request == null || name == null) {
15 | throw new IllegalArgumentException("参数为空!");
16 | }
17 |
18 | Cookie[] cookies = request.getCookies();
19 | if (cookies != null) {
20 | for (Cookie cookie : cookies) {
21 | if (cookie.getName().equals(name)) {
22 | return cookie.getValue();
23 | }
24 | }
25 | }
26 | return null;
27 | }
28 |
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/src/main/java/com/community/utils/Constant.java:
--------------------------------------------------------------------------------
1 | package com.community.utils;
2 |
3 | public class Constant {
4 |
5 | /**
6 | * 激活成功
7 | */
8 | public static int ACTIVATION_SUCCESS = 0;
9 |
10 | /**
11 | * 重复激活
12 | */
13 | public static int ACTIVATION_REPEAT = 1;
14 |
15 | /**
16 | * 激活失败
17 | */
18 | public static int ACTIVATION_FAILURE = 2;
19 |
20 | /**
21 | * 默认状态的登录凭证的超市时间(12小时)
22 | */
23 | public static int DEFAULT_EXPIRED_SECONDS = 3600 * 12;
24 |
25 | /**
26 | * 记住状态下的登录凭证的超时时间(3个月)
27 | */
28 | public static int REMEMBER_EXPIRED_SECONDS = 3600 * 12 * 100;
29 |
30 | /**
31 | * 实体类型:帖子
32 | */
33 | public static int ENTITY_TYPE_POST = 1;
34 |
35 | /**
36 | * 实体类型:评论
37 | */
38 | public static int ENTITY_TYPE_COMMENT = 2;
39 |
40 | /**
41 | * 实体类型:用户
42 | */
43 | public static int ENTITY_TYPE_USER = 3;
44 |
45 | }
46 |
--------------------------------------------------------------------------------
/src/main/java/com/community/vo/ResultVo.java:
--------------------------------------------------------------------------------
1 | package com.community.vo;
2 |
3 | import com.alibaba.fastjson.JSONObject;
4 |
5 | import java.util.Map;
6 |
7 | public class ResultVo {
8 |
9 | // 返回统一数据格式
10 | public static String getJsonString(int code, String msg, Map map) {
11 | JSONObject jsonObject = new JSONObject();
12 | jsonObject.put("code", code);
13 | jsonObject.put("msg", msg);
14 | if (map != null) {
15 | // 遍历 map 有三种方法,这里使用遍历 key 的方法
16 | for (String key : map.keySet()) {
17 | jsonObject.put(key, map.get(key));
18 | }
19 | }
20 | return jsonObject.toJSONString();
21 | }
22 |
23 | public static String getJsonString(int code, String msg) {
24 | return getJsonString(code, msg, null);
25 | }
26 |
27 | public static String getJsonString(int code) {
28 | return getJsonString(code, null, null);
29 | }
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/src/main/java/com/community/service/CommentService.java:
--------------------------------------------------------------------------------
1 | package com.community.service;
2 |
3 | import com.community.entity.Comment;
4 |
5 | import java.util.List;
6 |
7 | public interface CommentService {
8 |
9 | /**
10 | * 根据实体查询评论
11 | *
12 | * @param entityType 实体类型 0-帖子 1-评论
13 | * @param entityId 实体 id
14 | * @param offset 每页起始行行号
15 | * @param limit 一页显示多少条数据
16 | */
17 | List selectCommentByEntity(int entityType, int entityId, int offset, int limit);
18 |
19 | /**
20 | * 根据实体查询评论数
21 | *
22 | * @param entityType 实体类型 0-帖子 1-评论
23 | * @param entityId 实体 id
24 | */
25 | Long selectCount(Integer entityType, Integer entityId);
26 |
27 | /**
28 | * 增加评论
29 | */
30 | Boolean insertComment(Comment comment);
31 |
32 | /**
33 | * 根据用户 id 查询评论数
34 | */
35 | int selectCountByUserId(int userId);
36 |
37 |
38 | List selectCommentByUserId(int userId, int offset, int limit);
39 | }
40 |
--------------------------------------------------------------------------------
/src/main/java/com/community/service/FollowService.java:
--------------------------------------------------------------------------------
1 | package com.community.service;
2 |
3 | import java.util.List;
4 | import java.util.Map;
5 |
6 | public interface FollowService {
7 |
8 | /**
9 | * 关注
10 | */
11 | void follow(int userId, int entityType, int entityId);
12 |
13 | /**
14 | * 取消关注
15 | */
16 | void unfollow(int userId, int entityType, int entityId);
17 |
18 | /**
19 | * 查询某用户关注实体的数量
20 | */
21 | long selectFolloweeCount(int userId, int entityType);
22 |
23 | /**
24 | * 查询实体的粉丝的数量
25 | */
26 | long selectFollowerCount(int entityType, int entityId);
27 |
28 | /**
29 | * 查询当前用户是否已关注该实体
30 | */
31 | boolean hasFollowed(int userId, int entityType, int entityId);
32 |
33 | /**
34 | * 查询某用户关注的用户
35 | */
36 | List