├── src ├── main │ ├── resources │ │ ├── sensitive-words.txt │ │ ├── static │ │ │ ├── css │ │ │ │ ├── login.css │ │ │ │ ├── letter.css │ │ │ │ ├── discuss-detail.css │ │ │ │ └── global.css │ │ │ ├── img │ │ │ │ ├── 404.png │ │ │ │ ├── error.png │ │ │ │ └── captcha.png │ │ │ ├── js │ │ │ │ ├── register.js │ │ │ │ ├── discuss.js │ │ │ │ ├── index.js │ │ │ │ ├── letter.js │ │ │ │ ├── profile.js │ │ │ │ └── global.js │ │ │ └── html │ │ │ │ ├── student.html │ │ │ │ └── ajax-demo.html │ │ ├── application.properties │ │ ├── templates │ │ │ ├── demo │ │ │ │ └── view.html │ │ │ ├── mail │ │ │ │ ├── demo.html │ │ │ │ ├── forget.html │ │ │ │ └── activation.html │ │ │ ├── site │ │ │ │ ├── operate-result.html │ │ │ │ ├── admin │ │ │ │ │ └── data.html │ │ │ │ ├── forget.html │ │ │ │ ├── profile.html │ │ │ │ ├── register.html │ │ │ │ ├── followee.html │ │ │ │ └── follower.html │ │ │ └── error │ │ │ │ ├── 404.html │ │ │ │ └── 500.html │ │ ├── mapper │ │ │ ├── comment-mapper.xml │ │ │ ├── discusspost-mapper.xml │ │ │ ├── user-mapper.xml │ │ │ └── message-mapper.xml │ │ └── logback-spring.xml │ └── java │ │ └── com │ │ └── nowcoder │ │ └── community │ │ ├── dao │ │ ├── AlphaDao.java │ │ ├── AlphaDaoHibernateImpl.java │ │ ├── AlphaDaoMyBatisImpl.java │ │ ├── CommentMapper.java │ │ ├── UserMapper.java │ │ ├── DiscussPostMapper.java │ │ ├── LoginTicketMapper.java │ │ └── MessageMapper.java │ │ ├── entity │ │ ├── LoginTicket.java │ │ ├── Message.java │ │ ├── Comment.java │ │ ├── Page.java │ │ ├── DiscussPost.java │ │ └── User.java │ │ ├── CommunityApplication.java │ │ ├── annotation │ │ └── LoginRequired.java │ │ ├── config │ │ ├── AlphaConfig.java │ │ ├── WebMvcConfig.java │ │ ├── RedisConfig.java │ │ ├── KaptchaConfig.java │ │ └── interceptor │ │ │ ├── LoginRequiredInterceptor.java │ │ │ └── LoginTicketInterceptor.java │ │ ├── utils │ │ ├── HostHolder.java │ │ ├── CookieUtil.java │ │ ├── CommunityConstant.java │ │ ├── MailClient.java │ │ ├── CommunityUtil.java │ │ ├── RedisKeyUtil.java │ │ └── SensitiveFilter.java │ │ ├── service │ │ ├── CommentService.java │ │ ├── AlphaService.java │ │ ├── DiscussPostService.java │ │ ├── MessageService.java │ │ ├── LikeService.java │ │ ├── FollowService.java │ │ └── UserService.java │ │ ├── aspect │ │ ├── AlphaAspect.java │ │ └── ServiceLogAspect.java │ │ └── controller │ │ ├── advice │ │ └── ExceptionAdvice.java │ │ ├── LikeController.java │ │ ├── HomeController.java │ │ ├── FollowController.java │ │ ├── AlphaController.java │ │ ├── MessageController.java │ │ ├── DiscussPostController.java │ │ ├── UserController.java │ │ └── LoginController.java └── test │ └── java │ └── com │ └── nowcoder │ └── community │ ├── SensitiveTest.java │ ├── LoggerTests.java │ ├── MailTest.java │ ├── CommunityApplicationTests.java │ └── MapperTests.java ├── .mvn └── wrapper │ ├── maven-wrapper.jar │ ├── maven-wrapper.properties │ └── MavenWrapperDownloader.java ├── .gitignore ├── pom.xml ├── mvnw.cmd └── README.md /src/main/resources/sensitive-words.txt: -------------------------------------------------------------------------------- 1 | 赌博 2 | 吸毒 3 | 嫖娼 4 | 斗殴 5 | -------------------------------------------------------------------------------- /src/main/resources/static/css/login.css: -------------------------------------------------------------------------------- 1 | .main .container { 2 | width: 720px; 3 | } 4 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pallscall/NowcoderForum/HEAD/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /src/main/resources/static/img/404.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pallscall/NowcoderForum/HEAD/src/main/resources/static/img/404.png -------------------------------------------------------------------------------- /src/main/resources/static/img/error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pallscall/NowcoderForum/HEAD/src/main/resources/static/img/error.png -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pallscall/NowcoderForum/HEAD/src/main/resources/application.properties -------------------------------------------------------------------------------- /src/main/resources/static/img/captcha.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pallscall/NowcoderForum/HEAD/src/main/resources/static/img/captcha.png -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.0/apache-maven-3.6.0-bin.zip 2 | -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/community/dao/AlphaDao.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.community.dao; 2 | 3 | public interface AlphaDao { 4 | 5 | String select(); 6 | 7 | } 8 | -------------------------------------------------------------------------------- /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/templates/demo/view.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Teacher 6 | 7 | 8 |

9 |

10 | 11 | -------------------------------------------------------------------------------- /src/main/resources/templates/mail/demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Title 6 | 7 | 8 |

欢迎你,

9 | 10 | -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/community/dao/AlphaDaoHibernateImpl.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.community.dao; 2 | 3 | import org.springframework.stereotype.Repository; 4 | 5 | @Repository("alphaHibernate") 6 | public class AlphaDaoHibernateImpl implements AlphaDao { 7 | @Override 8 | public String select() { 9 | return "Hibernate"; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /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/nowcoder/community/entity/LoginTicket.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.community.entity; 2 | 3 | import lombok.Data; 4 | 5 | import java.util.Date; 6 | 7 | /** 8 | * 登录凭证实体类 9 | */ 10 | @Data 11 | public class LoginTicket { 12 | private int id; 13 | private int userId; 14 | private String ticket; 15 | private int status; 16 | private Date expired; 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/community/dao/AlphaDaoMyBatisImpl.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.community.dao; 2 | 3 | import org.springframework.context.annotation.Primary; 4 | import org.springframework.stereotype.Repository; 5 | 6 | @Repository 7 | @Primary 8 | public class AlphaDaoMyBatisImpl implements AlphaDao{ 9 | @Override 10 | public String select() { 11 | return "MyBatis"; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/community/CommunityApplication.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.community; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class CommunityApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(CommunityApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/community/entity/Message.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.community.entity; 2 | 3 | import lombok.Data; 4 | 5 | import java.util.Date; 6 | 7 | /** 8 | * 私信 9 | */ 10 | @Data 11 | public class Message { 12 | private int id; 13 | private int fromId; 14 | private int toId; 15 | private String conversationId; 16 | private String content; 17 | private int status; 18 | private Date createTime; 19 | } 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | /target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | 5 | ### STS ### 6 | .apt_generated 7 | .classpath 8 | .factorypath 9 | .project 10 | .settings 11 | .springBeans 12 | .sts4-cache 13 | 14 | ### IntelliJ IDEA ### 15 | .idea 16 | *.iws 17 | *.iml 18 | *.ipr 19 | 20 | ### NetBeans ### 21 | /nbproject/private/ 22 | /nbbuild/ 23 | /dist/ 24 | /nbdist/ 25 | /.nb-gradle/ 26 | /build/ 27 | 28 | ### VS Code ### 29 | .vscode/ 30 | -------------------------------------------------------------------------------- /src/main/resources/static/js/register.js: -------------------------------------------------------------------------------- 1 | $(function(){ 2 | $("form").submit(check_data); 3 | $("input").focus(clear_error); 4 | }); 5 | 6 | function check_data() { 7 | var pwd1 = $("#password").val(); 8 | var pwd2 = $("#confirm-password").val(); 9 | if(pwd1 != pwd2) { 10 | $("#confirm-password").addClass("is-invalid"); 11 | return false; 12 | } 13 | return true; 14 | } 15 | 16 | function clear_error() { 17 | $(this).removeClass("is-invalid"); 18 | } -------------------------------------------------------------------------------- /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/nowcoder/community/dao/CommentMapper.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.community.dao; 2 | 3 | import com.nowcoder.community.entity.Comment; 4 | import org.apache.ibatis.annotations.Mapper; 5 | 6 | import java.util.List; 7 | 8 | @Mapper 9 | public interface CommentMapper { 10 | 11 | List selectCommentsByEntity(int entityType, int entityId, int offset, int limit); 12 | 13 | int selectCountByEntity(int entityType, int entityId); 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/community/entity/Comment.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.community.entity; 2 | 3 | import lombok.Data; 4 | 5 | import java.util.Date; 6 | 7 | /** 8 | * 评论 9 | */ 10 | @Data 11 | public class Comment { 12 | 13 | private int id; 14 | private int userId; 15 | private int entityType; 16 | private int entityId; 17 | private int targetId; 18 | private String content; 19 | private int status; 20 | private Date createTime; 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/community/annotation/LoginRequired.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.community.annotation; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * 该注解表示是否需要登录 10 | */ 11 | @Target(ElementType.METHOD) //作用在方法上 12 | @Retention(RetentionPolicy.RUNTIME) //运行时生效 13 | public @interface LoginRequired { 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/community/config/AlphaConfig.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.community.config; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | 6 | import java.text.SimpleDateFormat; 7 | 8 | @Configuration 9 | public class AlphaConfig { 10 | 11 | @Bean 12 | public SimpleDateFormat simpleDateFormat() { 13 | return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/resources/templates/mail/activation.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 牛客网-激活账号 7 | 8 | 9 |
10 |

11 | , 您好! 12 |

13 |

14 | 您正在注册牛客网, 这是一封激活邮件, 请点击 15 | 此链接, 16 | 激活您的牛客账号! 17 |

18 |
19 | 20 | -------------------------------------------------------------------------------- /src/main/resources/static/html/student.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 增加学生 6 | 7 | 8 | 9 |
10 |

11 | 姓名: 12 |

13 |

14 | 年龄: 15 |

16 |

17 | 18 |

19 |
20 | 21 | 22 | -------------------------------------------------------------------------------- /src/main/resources/static/js/discuss.js: -------------------------------------------------------------------------------- 1 | function like(btn, entityType, entityId, entityUserId) { 2 | $.post( 3 | CONTEXT_PATH + "/like", 4 | {"entityType":entityType,"entityId":entityId,"entityUserId":entityUserId}, 5 | function(data) { 6 | data = $.parseJSON(data); 7 | if(data.code == 0) { 8 | $(btn).children("i").text(data.likeCount); 9 | $(btn).children("b").text(data.likeStatus==1?'已赞':"赞"); 10 | } else { 11 | alert(data.msg); 12 | } 13 | } 14 | ); 15 | } -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/community/dao/UserMapper.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.community.dao; 2 | 3 | import com.nowcoder.community.entity.User; 4 | import org.apache.ibatis.annotations.Mapper; 5 | 6 | @Mapper 7 | public interface UserMapper { 8 | 9 | User selectById(int id); 10 | 11 | User selectByName(String username); 12 | 13 | User selectByEmail(String email); 14 | 15 | int insertUser(User user); 16 | 17 | int updateStatus(int id, int status); 18 | 19 | int updateHeader(int id, String headerUrl); 20 | 21 | int updatePassword(int id, String password); 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/community/utils/HostHolder.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.community.utils; 2 | 3 | import com.nowcoder.community.entity.User; 4 | import org.springframework.stereotype.Component; 5 | 6 | /** 7 | * 持有用户信息,用于代替session对象 8 | */ 9 | @Component 10 | public class HostHolder { 11 | 12 | private ThreadLocal users = new ThreadLocal(); 13 | 14 | public void setUser(User user){ 15 | users.set(user); 16 | } 17 | public User getUser(){ 18 | return users.get(); 19 | } 20 | 21 | public void clear(){ 22 | users.remove(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/community/dao/DiscussPostMapper.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.community.dao; 2 | 3 | import com.nowcoder.community.entity.DiscussPost; 4 | import org.apache.ibatis.annotations.Mapper; 5 | import org.apache.ibatis.annotations.Param; 6 | 7 | import java.util.List; 8 | 9 | @Mapper 10 | public interface DiscussPostMapper { 11 | 12 | List selectDiscussPosts(int userId, int offset, int limit); 13 | 14 | // @Param注解用于给参数取别名, 15 | // 如果只有一个参数,并且在里使用,则必须加别名. 16 | int selectDiscussPostRows(@Param("userId") int userId); 17 | 18 | int insertDiscussPost(DiscussPost discussPost); 19 | DiscussPost selectDiscussPostById(int id); 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/community/utils/CookieUtil.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.community.utils; 2 | 3 | 4 | import javax.servlet.http.Cookie; 5 | import javax.servlet.http.HttpServletRequest; 6 | 7 | public class CookieUtil { 8 | public static String getValue(HttpServletRequest request, String name){ 9 | if(request == null || name == null){ 10 | throw new IllegalArgumentException("参数为空!"); 11 | } 12 | 13 | Cookie[] cookies = request.getCookies(); 14 | if(cookies != null){ 15 | for(Cookie cookie: cookies){ 16 | if(cookie.getName().equals(name)){ 17 | return cookie.getValue(); 18 | } 19 | } 20 | } 21 | return null; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/community/dao/LoginTicketMapper.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.community.dao; 2 | 3 | import com.nowcoder.community.entity.LoginTicket; 4 | import org.apache.ibatis.annotations.*; 5 | 6 | @Mapper 7 | @Deprecated //废弃该组件 8 | public interface LoginTicketMapper { 9 | 10 | @Insert("insert into login_ticket(user_id,ticket,status,expired) " + 11 | "values" + 12 | "(#{userId},#{ticket},#{status},#{expired})" 13 | ) 14 | @Options(useGeneratedKeys = true, keyProperty = "id") 15 | int insertLoinTicket(LoginTicket loginTicket); 16 | 17 | @Select("select * from login_ticket where ticket=#{ticket}") 18 | LoginTicket selectByTicket(String ticket); 19 | 20 | @Update("update login_ticket set status=#{status} where ticket=#{ticket}") 21 | int updateStatus(String ticket,int status); //更改凭证状态 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/community/service/CommentService.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.community.service; 2 | 3 | import com.nowcoder.community.dao.CommentMapper; 4 | import com.nowcoder.community.entity.Comment; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.stereotype.Service; 7 | 8 | import java.util.List; 9 | 10 | @Service 11 | public class CommentService { 12 | 13 | @Autowired 14 | private CommentMapper commentMapper; 15 | 16 | public List selectCommentsByEntity(int entityType, int entityId, int offset, int limit){ 17 | return commentMapper.selectCommentsByEntity(entityType, entityId, offset, limit); 18 | } 19 | 20 | public int selectCountByEntity(int entityType, int entityId){ 21 | return commentMapper.selectCountByEntity(entityType,entityId); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/test/java/com/nowcoder/community/SensitiveTest.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.community; 2 | 3 | import com.nowcoder.community.utils.SensitiveFilter; 4 | import org.junit.Test; 5 | import org.junit.runner.RunWith; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.test.context.SpringBootTest; 8 | import org.springframework.test.context.ContextConfiguration; 9 | import org.springframework.test.context.junit4.SpringRunner; 10 | 11 | @RunWith(SpringRunner.class) 12 | @SpringBootTest 13 | @ContextConfiguration(classes = CommunityApplication.class) 14 | public class SensitiveTest { 15 | 16 | @Autowired 17 | private SensitiveFilter sensitiveFilter; 18 | 19 | @Test 20 | public void testSensitiveFilter(){ 21 | String text = "赌博 嫖 娼 哈哈哈 斗素数筛殴"; 22 | text = sensitiveFilter.filter(text); 23 | System.out.println(text); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/resources/static/js/index.js: -------------------------------------------------------------------------------- 1 | $(function(){ 2 | $("#publishBtn").click(publish); 3 | }); 4 | 5 | function publish() { 6 | $("#publishModal").modal("hide"); 7 | 8 | // 获取标题和内容 9 | var title = $("#recipient-name").val(); 10 | var content = $("#message-text").val(); 11 | // 发送异步请求(POST) 12 | $.post( 13 | CONTEXT_PATH + "/discuss/add", 14 | {"title":title,"content":content}, 15 | function(data) { 16 | data = $.parseJSON(data); 17 | // 在提示框中显示返回消息 18 | $("#hintBody").text(data.msg); 19 | // 显示提示框 20 | $("#hintModal").modal("show"); 21 | // 2秒后,自动隐藏提示框 22 | setTimeout(function(){ 23 | $("#hintModal").modal("hide"); 24 | // 刷新页面 25 | if(data.code == 0) { 26 | window.location.reload(); 27 | } 28 | }, 2000); 29 | } 30 | ); 31 | 32 | } -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/community/utils/CommunityConstant.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.community.utils; 2 | 3 | /** 4 | * 常量接口 5 | */ 6 | public interface CommunityConstant { 7 | /** 8 | * 激活成功 9 | */ 10 | int ACTIVATION_SUCCESS = 0; 11 | 12 | /** 13 | * 重复激活 14 | */ 15 | int ACTIVATION_REPEAT = 1; 16 | 17 | /** 18 | * 激活失败 19 | */ 20 | int ACTIVATION_FAILURE = 2; 21 | 22 | /** 23 | * 登录信息的默认超时时间,单位s 24 | */ 25 | int DEFAULT_EXPIRED_SECONDS = 3600*12; //12个小时 26 | 27 | /** 28 | * 记住我后的超时时间,单位s 29 | */ 30 | int REMEMBER_EXPIRED_SECONDS = 3600*24*100; //100天 31 | 32 | /** 33 | * 实体类型:帖子 34 | */ 35 | int ENTITY_TYPE_POST = 1; 36 | 37 | /** 38 | * 实体类型:评论 39 | */ 40 | int ENTITY_TYPE_COMMENT = 2; 41 | 42 | /** 43 | * 实体类型:用户 44 | */ 45 | int ENTITY_TYPE_USER = 3; 46 | } 47 | 48 | 49 | -------------------------------------------------------------------------------- /src/test/java/com/nowcoder/community/LoggerTests.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.community; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.springframework.boot.test.context.SpringBootTest; 8 | import org.springframework.test.context.ContextConfiguration; 9 | import org.springframework.test.context.junit4.SpringRunner; 10 | 11 | @RunWith(SpringRunner.class) 12 | @SpringBootTest 13 | @ContextConfiguration(classes = CommunityApplication.class) 14 | public class LoggerTests { 15 | 16 | private static final Logger logger = LoggerFactory.getLogger(LoggerTests.class); 17 | 18 | @Test 19 | public void testLogger() { 20 | System.out.println(logger.getName()); 21 | 22 | logger.debug("debug log"); 23 | logger.info("info log"); 24 | logger.warn("warn log"); 25 | logger.error("error log"); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/community/dao/MessageMapper.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.community.dao; 2 | 3 | import com.nowcoder.community.entity.Message; 4 | import org.apache.ibatis.annotations.Mapper; 5 | 6 | import java.util.List; 7 | 8 | @Mapper 9 | public interface MessageMapper { 10 | 11 | // 查询当前用户的会话列表,针对每个会话只返回一条最新的私信. 12 | List selectConversations(int userId, int offset, int limit); 13 | 14 | // 查询当前用户的会话数量. 15 | int selectConversationCount(int userId); 16 | 17 | // 查询某个会话所包含的私信列表. 18 | List selectLetters(String conversationId, int offset, int limit); 19 | 20 | // 查询某个会话所包含的私信数量. 21 | int selectLetterCount(String conversationId); 22 | 23 | // 查询未读私信的数量 24 | int selectLetterUnreadCount(int userId, String conversationId); 25 | 26 | // 新增消息 27 | int insertMessage(Message message); 28 | 29 | // 修改消息的状态 30 | int updateStatus(List ids, int status); 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/resources/static/html/ajax-demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | AJAX 6 | 7 | 8 |

9 | 10 |

11 | 12 | 13 | 30 | 31 | -------------------------------------------------------------------------------- /src/main/resources/static/js/letter.js: -------------------------------------------------------------------------------- 1 | $(function(){ 2 | $("#sendBtn").click(send_letter); 3 | $(".close").click(delete_msg); 4 | }); 5 | 6 | function send_letter() { 7 | $("#sendModal").modal("hide"); 8 | 9 | var toName = $("#recipient-name").val(); 10 | var content = $("#message-text").val(); 11 | $.post( 12 | CONTEXT_PATH + "/letter/send", 13 | {"toName":toName,"content":content}, 14 | function(data) { 15 | data = $.parseJSON(data); 16 | if(data.code == 0) { 17 | $("#hintBody").text("发送成功!"); 18 | } else { 19 | $("#hintBody").text(data.msg); 20 | } 21 | 22 | $("#hintModal").modal("show"); 23 | setTimeout(function(){ 24 | $("#hintModal").modal("hide"); 25 | location.reload(); 26 | }, 2000); 27 | } 28 | ); 29 | } 30 | 31 | function delete_msg() { 32 | // TODO 删除数据 33 | $(this).parents(".media").remove(); 34 | } -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/community/service/AlphaService.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.community.service; 2 | 3 | import com.nowcoder.community.dao.AlphaDao; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.context.annotation.Scope; 6 | import org.springframework.stereotype.Service; 7 | 8 | import javax.annotation.PostConstruct; 9 | import javax.annotation.PreDestroy; 10 | 11 | @Service 12 | //@Scope("prototype") 13 | public class AlphaService { 14 | 15 | @Autowired 16 | private AlphaDao alphaDao; 17 | 18 | public AlphaService() { 19 | // System.out.println("实例化AlphaService"); 20 | } 21 | 22 | @PostConstruct 23 | public void init() { 24 | // System.out.println("初始化AlphaService"); 25 | } 26 | 27 | @PreDestroy 28 | public void destroy() { 29 | // System.out.println("销毁AlphaService"); 30 | } 31 | 32 | public String find() { 33 | return alphaDao.select(); 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/main/resources/static/js/profile.js: -------------------------------------------------------------------------------- 1 | $(function(){ 2 | $(".follow-btn").click(follow); 3 | }); 4 | 5 | function follow() { 6 | var btn = this; 7 | if($(btn).hasClass("btn-info")) { 8 | // 关注TA 9 | $.post( 10 | CONTEXT_PATH + "/follow", 11 | {"entityType":3,"entityId":$(btn).prev().val()}, 12 | function(data) { 13 | data = $.parseJSON(data); 14 | if(data.code == 0) { 15 | window.location.reload(); 16 | } else { 17 | alert(data.msg); 18 | } 19 | } 20 | ); 21 | // $(btn).text("已关注").removeClass("btn-info").addClass("btn-secondary"); 22 | } else { 23 | // 取消关注 24 | $.post( 25 | CONTEXT_PATH + "/unfollow", 26 | {"entityType":3,"entityId":$(btn).prev().val()}, 27 | function(data) { 28 | data = $.parseJSON(data); 29 | if(data.code == 0) { 30 | window.location.reload(); 31 | } else { 32 | alert(data.msg); 33 | } 34 | } 35 | ); 36 | //$(btn).text("关注TA").removeClass("btn-secondary").addClass("btn-info"); 37 | } 38 | } -------------------------------------------------------------------------------- /src/main/resources/mapper/comment-mapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | id,user_id,entity_type,entity_id,target_id,content,status,create_time 9 | 10 | 11 | 20 | 21 | 28 | -------------------------------------------------------------------------------- /src/main/resources/static/js/global.js: -------------------------------------------------------------------------------- 1 | var CONTEXT_PATH = "/community"; 2 | 3 | window.alert = function(message) { 4 | if(!$(".alert-box").length) { 5 | $("body").append( 6 | '' 24 | ); 25 | } 26 | 27 | var h = $(".alert-box").height(); 28 | var y = h / 2 - 100; 29 | if(h > 600) y -= 100; 30 | $(".alert-box .modal-dialog").css("margin", (y < 0 ? 0 : y) + "px auto"); 31 | 32 | $(".alert-box .modal-body p").text(message); 33 | $(".alert-box").modal("show"); 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/community/config/WebMvcConfig.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.community.config; 2 | 3 | import com.nowcoder.community.config.interceptor.LoginRequiredInterceptor; 4 | import com.nowcoder.community.config.interceptor.LoginTicketInterceptor; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.context.annotation.Configuration; 7 | import org.springframework.web.servlet.config.annotation.InterceptorRegistry; 8 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 9 | 10 | @Configuration 11 | public class WebMvcConfig implements WebMvcConfigurer { 12 | 13 | @Autowired 14 | private LoginTicketInterceptor loginTicketInterceptor; 15 | 16 | @Autowired 17 | private LoginRequiredInterceptor loginRequiredInterceptor; 18 | @Override 19 | public void addInterceptors(InterceptorRegistry registry) { 20 | registry.addInterceptor(loginTicketInterceptor) 21 | .excludePathPatterns("/**/*.css","/**/*.js","/**/*.png","/**/*.jpg","/**/*.jpeg"); 22 | 23 | registry.addInterceptor(loginRequiredInterceptor) 24 | .excludePathPatterns("/**/*.css","/**/*.js","/**/*.png","/**/*.jpg","/**/*.jpeg"); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/community/config/RedisConfig.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.community.config; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.data.redis.connection.RedisConnectionFactory; 6 | import org.springframework.data.redis.core.RedisTemplate; 7 | import org.springframework.data.redis.serializer.RedisSerializer; 8 | 9 | /** 10 | * Redis序列化 11 | */ 12 | @Configuration 13 | public class RedisConfig { 14 | 15 | @Bean 16 | public RedisTemplate redisTemplate(RedisConnectionFactory factory) { 17 | RedisTemplate template = new RedisTemplate<>(); 18 | template.setConnectionFactory(factory); 19 | 20 | // 设置key的序列化方式 21 | template.setKeySerializer(RedisSerializer.string()); 22 | // 设置value的序列化方式 23 | template.setValueSerializer(RedisSerializer.json()); 24 | // 设置hash的key的序列化方式 25 | template.setHashKeySerializer(RedisSerializer.string()); 26 | // 设置hash的value的序列化方式 27 | template.setHashValueSerializer(RedisSerializer.json()); 28 | 29 | template.afterPropertiesSet(); 30 | return template; 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/community/aspect/AlphaAspect.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.community.aspect; 2 | 3 | import org.aspectj.lang.ProceedingJoinPoint; 4 | import org.aspectj.lang.annotation.*; 5 | import org.springframework.stereotype.Component; 6 | 7 | //@Component 8 | //@Aspect 9 | public class AlphaAspect { 10 | 11 | @Pointcut("execution(* com.nowcoder.community.service.*.*(..))") 12 | public void pointcut() { 13 | 14 | } 15 | 16 | @Before("pointcut()") 17 | public void before() { 18 | System.out.println("before"); 19 | } 20 | 21 | @After("pointcut()") 22 | public void after() { 23 | System.out.println("after"); 24 | } 25 | 26 | @AfterReturning("pointcut()") 27 | public void afterRetuning() { 28 | System.out.println("afterRetuning"); 29 | } 30 | 31 | @AfterThrowing("pointcut()") 32 | public void afterThrowing() { 33 | System.out.println("afterThrowing"); 34 | } 35 | 36 | @Around("pointcut()") 37 | public Object around(ProceedingJoinPoint joinPoint) throws Throwable { 38 | System.out.println("around before"); 39 | Object obj = joinPoint.proceed(); 40 | System.out.println("around after"); 41 | return obj; 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/test/java/com/nowcoder/community/MailTest.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.community; 2 | 3 | import com.nowcoder.community.utils.MailClient; 4 | import net.bytebuddy.asm.Advice; 5 | import org.junit.Test; 6 | import org.junit.runner.RunWith; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.boot.test.context.SpringBootTest; 9 | import org.springframework.test.context.ContextConfiguration; 10 | import org.springframework.test.context.junit4.SpringRunner; 11 | import org.thymeleaf.TemplateEngine; 12 | import org.thymeleaf.context.Context; 13 | 14 | @RunWith(SpringRunner.class) 15 | @SpringBootTest 16 | @ContextConfiguration(classes = CommunityApplication.class) 17 | public class MailTest { 18 | 19 | @Autowired 20 | private MailClient mailClient; 21 | 22 | @Autowired 23 | private TemplateEngine templateEngine; 24 | 25 | @Test 26 | public void testTextMail(){ 27 | mailClient.sendMail("306698601@qq.com","test","emailtest"); 28 | } 29 | 30 | /** 31 | * 发送html邮件 32 | */ 33 | @Test 34 | public void testHtmlMail(){ 35 | Context context = new Context(); 36 | context.setVariable("username","sandy"); 37 | 38 | String content = templateEngine.process("/mail/demo", context); 39 | mailClient.sendMail("306698601@qq.com","test",content); 40 | 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/community/config/KaptchaConfig.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.community.config; 2 | 3 | import com.google.code.kaptcha.Producer; 4 | import com.google.code.kaptcha.impl.DefaultKaptcha; 5 | import com.google.code.kaptcha.util.Config; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | 9 | import java.util.*; 10 | 11 | @Configuration 12 | public class KaptchaConfig { 13 | 14 | @Bean 15 | public Producer kaptchaProducer(){ 16 | Properties properties = new Properties(); 17 | //验证码 18 | properties.setProperty("kaptcha.image.width","100"); //长度 19 | properties.setProperty("kaptcha.image.height","40"); //高度 20 | properties.setProperty("kaptcha.textproducer.font.size","32"); //字体大小 21 | properties.setProperty("kaptcha.textproducer.font.color","0,0,0"); //颜色 22 | properties.setProperty("kaptcha.textproducer.char.string","0123456789qwertyuioplkjhgfdszxcvbnm"); //随机字 23 | properties.setProperty("kaptcha.textproducer.char.length","4"); //长度 24 | properties.setProperty("kaptcha.noise.impl","com.google.code.kaptcha.impl.NoNoise"); //干扰 25 | 26 | DefaultKaptcha defaultKaptcha = new DefaultKaptcha(); 27 | Config config = new Config(properties); 28 | defaultKaptcha.setConfig(config); 29 | return defaultKaptcha; 30 | 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/community/utils/MailClient.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.community.utils; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.beans.factory.annotation.Value; 7 | import org.springframework.mail.javamail.JavaMailSender; 8 | import org.springframework.mail.javamail.MimeMessageHelper; 9 | import org.springframework.stereotype.Component; 10 | 11 | import javax.mail.MessagingException; 12 | import javax.mail.internet.MimeMessage; 13 | 14 | @Component 15 | public class MailClient { 16 | private static final Logger logger = LoggerFactory.getLogger(MailClient.class); 17 | 18 | @Autowired 19 | private JavaMailSender mailSender; 20 | 21 | @Value("${spring.mail.username}") 22 | private String from; 23 | 24 | public void sendMail(String to,String subject,String content){ 25 | try { 26 | MimeMessage message = mailSender.createMimeMessage(); 27 | MimeMessageHelper helper = new MimeMessageHelper(message); 28 | helper.setFrom(from); 29 | helper.setTo(to); 30 | helper.setSubject(subject); 31 | helper.setText(content,true); //允许支持html文本 32 | mailSender.send(helper.getMimeMessage()); 33 | } catch (MessagingException e) { 34 | logger.error("发送邮件失败:"+e.getMessage()); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/community/config/interceptor/LoginRequiredInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.community.config.interceptor; 2 | 3 | import com.nowcoder.community.annotation.LoginRequired; 4 | import com.nowcoder.community.utils.HostHolder; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.stereotype.Component; 7 | import org.springframework.web.method.HandlerMethod; 8 | import org.springframework.web.servlet.HandlerInterceptor; 9 | 10 | import javax.servlet.http.HttpServletRequest; 11 | import javax.servlet.http.HttpServletResponse; 12 | import java.lang.reflect.Method; 13 | 14 | @Component 15 | public class LoginRequiredInterceptor implements HandlerInterceptor { 16 | @Autowired 17 | private HostHolder hostHolder; 18 | 19 | 20 | @Override 21 | public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { 22 | if(handler instanceof HandlerMethod){ //判断拦截目标是否是一个方法 23 | HandlerMethod handlerMethod = (HandlerMethod)handler; 24 | Method method = handlerMethod.getMethod(); 25 | LoginRequired annotation = method.getAnnotation(LoginRequired.class); 26 | if(annotation != null && hostHolder.getUser() == null){ //注解不为空拦截的方法需要登录才能实现,hostHolder.geUser()为空表示当前没有用户登录 27 | response.sendRedirect(request.getContextPath()+"/login"); 28 | return false; 29 | } 30 | } 31 | return true; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/community/utils/CommunityUtil.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.community.utils; 2 | 3 | 4 | import com.alibaba.fastjson.JSONObject; 5 | import org.apache.commons.lang3.StringUtils; 6 | import org.springframework.util.DigestUtils; 7 | 8 | import java.util.Map; 9 | import java.util.UUID; 10 | 11 | public class CommunityUtil { 12 | 13 | // 生成随机字符串 14 | public static String generateUUID(){ 15 | return UUID.randomUUID().toString().replaceAll("-",""); 16 | } 17 | 18 | // MD5加密 19 | //真实密码+盐 --md5--> 加密密码 20 | //盐:随机字符串 21 | public static String md5(String key){ 22 | if(StringUtils.isBlank(key)) return null; 23 | 24 | return DigestUtils.md5DigestAsHex(key.getBytes()); 25 | } 26 | 27 | /** 28 | * 将浏览器返回的数据转成json字符串 29 | * @param code 响应码 30 | * @param msg 响应信息 31 | * @param map 业务信息 32 | * @return 33 | */ 34 | public static String getJsonString (int code, String msg, Map map){ 35 | JSONObject json = new JSONObject(); 36 | json.put("code",code); 37 | json.put("msg",msg); 38 | if(map != null){ 39 | for(String key: map.keySet()){ 40 | json.put(key,map.get(key)); 41 | } 42 | } 43 | 44 | return json.toJSONString(); 45 | } 46 | public static String getJsonString (int code, String msg){ 47 | return getJsonString(code, msg, null); 48 | } 49 | public static String getJsonString (int code){ 50 | return getJsonString(code, null, null); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/community/controller/advice/ExceptionAdvice.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.community.controller.advice; 2 | 3 | import com.nowcoder.community.utils.CommunityUtil; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.springframework.stereotype.Controller; 7 | import org.springframework.web.bind.annotation.ControllerAdvice; 8 | import org.springframework.web.bind.annotation.ExceptionHandler; 9 | 10 | import javax.servlet.http.HttpServletRequest; 11 | import javax.servlet.http.HttpServletResponse; 12 | import java.io.IOException; 13 | import java.io.PrintWriter; 14 | 15 | @ControllerAdvice(annotations = Controller.class) 16 | public class ExceptionAdvice { 17 | 18 | private static final Logger logger = LoggerFactory.getLogger(ExceptionAdvice.class); 19 | 20 | @ExceptionHandler({Exception.class}) 21 | public void handleException(Exception e, HttpServletRequest request, HttpServletResponse response) throws IOException { 22 | logger.error("服务器发生异常: " + e.getMessage()); 23 | for (StackTraceElement element : e.getStackTrace()) { 24 | logger.error(element.toString()); 25 | } 26 | 27 | String xRequestedWith = request.getHeader("x-requested-with"); //获取请求方式,同步/异步 28 | if ("XMLHttpRequest".equals(xRequestedWith)) { //异步 29 | response.setContentType("application/plain;charset=utf-8"); 30 | PrintWriter writer = response.getWriter(); 31 | writer.write(CommunityUtil.getJsonString(1, "服务器异常!")); 32 | } else { 33 | response.sendRedirect(request.getContextPath() + "/error"); 34 | } 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/main/resources/mapper/discusspost-mapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | id, user_id, title, content, type, status, create_time, comment_count, score 9 | 10 | 11 | 12 | user_id, title, content, type, status, create_time, comment_count, score 13 | 14 | 15 | 25 | 26 | 34 | 35 | 40 | 41 | 42 | insert into discuss_post() 43 | values (#{userId},#{title},#{content},#{type},#{status},#{createTime},#{commentCount},#{score}) 44 | 45 | -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/community/controller/LikeController.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.community.controller; 2 | 3 | import com.nowcoder.community.annotation.LoginRequired; 4 | import com.nowcoder.community.entity.User; 5 | import com.nowcoder.community.service.LikeService; 6 | import com.nowcoder.community.utils.CommunityUtil; 7 | import com.nowcoder.community.utils.HostHolder; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.stereotype.Controller; 10 | import org.springframework.web.bind.annotation.RequestMapping; 11 | import org.springframework.web.bind.annotation.RequestMethod; 12 | import org.springframework.web.bind.annotation.ResponseBody; 13 | 14 | import java.util.HashMap; 15 | import java.util.Map; 16 | 17 | @Controller 18 | public class LikeController { 19 | 20 | @Autowired 21 | private LikeService likeService; 22 | 23 | @Autowired 24 | private HostHolder hostHolder; 25 | 26 | @RequestMapping(path = "/like", method = RequestMethod.POST) 27 | @ResponseBody 28 | @LoginRequired 29 | public String like(int entityType, int entityId, int entityUserId){ 30 | User user = hostHolder.getUser(); 31 | 32 | //实现点赞 33 | likeService.like(user.getId(),entityType,entityId,entityUserId); 34 | //数量 35 | long entityLikeCount = likeService.findEntityLikeCount(entityType, entityId); 36 | //状态 37 | int entityLikeStatus = likeService.findEntityLikeStatus(user.getId(), entityType, entityId); 38 | 39 | //返回结果 40 | Map map = new HashMap<>(); 41 | map.put("likeCount",entityLikeCount); 42 | map.put("likeStatus",entityLikeStatus); 43 | 44 | return CommunityUtil.getJsonString(0,null,map); 45 | 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/community/service/DiscussPostService.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.community.service; 2 | 3 | import com.nowcoder.community.dao.DiscussPostMapper; 4 | import com.nowcoder.community.entity.DiscussPost; 5 | import com.nowcoder.community.utils.SensitiveFilter; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.stereotype.Service; 8 | import org.springframework.web.util.HtmlUtils; 9 | 10 | import java.util.List; 11 | 12 | @Service 13 | public class DiscussPostService { 14 | 15 | @Autowired 16 | private DiscussPostMapper discussPostMapper; 17 | 18 | @Autowired 19 | private SensitiveFilter sensitiveFilter; 20 | 21 | public List findDiscussPosts(int userId, int offset, int limit) { 22 | return discussPostMapper.selectDiscussPosts(userId, offset, limit); 23 | } 24 | 25 | public int findDiscussPostRows(int userId) { 26 | return discussPostMapper.selectDiscussPostRows(userId); 27 | } 28 | 29 | public int addDiscussPost(DiscussPost discussPost){ 30 | if(discussPost == null){ 31 | throw new IllegalArgumentException("参数不能为空!"); 32 | } 33 | //标题内容去标签 34 | discussPost.setTitle(HtmlUtils.htmlEscape(discussPost.getTitle())); 35 | discussPost.setContent(HtmlUtils.htmlEscape(discussPost.getContent())); 36 | //过滤敏感词 37 | discussPost.setTitle(sensitiveFilter.filter(discussPost.getTitle())); 38 | discussPost.setContent(sensitiveFilter.filter(discussPost.getContent())); 39 | 40 | return discussPostMapper.insertDiscussPost(discussPost); 41 | } 42 | 43 | public DiscussPost findDiscussPostById(int id){ 44 | return discussPostMapper.selectDiscussPostById(id); 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/community/aspect/ServiceLogAspect.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.community.aspect; 2 | 3 | import org.aspectj.lang.JoinPoint; 4 | import org.aspectj.lang.annotation.Aspect; 5 | import org.aspectj.lang.annotation.Before; 6 | import org.aspectj.lang.annotation.Pointcut; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import org.springframework.stereotype.Component; 10 | import org.springframework.web.context.request.RequestContextHolder; 11 | import org.springframework.web.context.request.ServletRequestAttributes; 12 | 13 | import javax.servlet.http.HttpServletRequest; 14 | import java.text.SimpleDateFormat; 15 | import java.util.Date; 16 | 17 | @Component 18 | @Aspect 19 | public class ServiceLogAspect { 20 | 21 | private static final Logger logger = LoggerFactory.getLogger(ServiceLogAspect.class); 22 | 23 | /** 24 | * 对业务层逻辑统一记录日志 25 | */ 26 | @Pointcut("execution(* com.nowcoder.community.service.*.*(..))") //切点作用范围 27 | public void pointcut() { 28 | 29 | } 30 | 31 | /** 32 | * 前置通知 33 | * @param joinPoint 连接的目标 34 | */ 35 | @Before("pointcut()") 36 | public void before(JoinPoint joinPoint) { 37 | // 用户[1.2.3.4],在[xxx],访问了[com.nowcoder.community.service.xxx()]. 38 | ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); 39 | HttpServletRequest request = attributes.getRequest(); //获取request对象 40 | String ip = request.getRemoteHost(); 41 | String now = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()); 42 | String target = joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName(); //组件名+方法名 43 | logger.info(String.format("用户[%s],在[%s],访问了[%s].", ip, now, target)); 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/main/resources/mapper/user-mapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | username, password, salt, email, type, status, activation_code, header_url, create_time 9 | 10 | 11 | 12 | id, username, password, salt, email, type, status, activation_code, header_url, create_time 13 | 14 | 15 | 20 | 21 | 26 | 27 | 32 | 33 | 34 | insert into user () 35 | values(#{username}, #{password}, #{salt}, #{email}, #{type}, #{status}, #{activationCode}, #{headerUrl}, #{createTime}) 36 | 37 | 38 | 39 | update user set status = #{status} where id = #{id} 40 | 41 | 42 | 43 | update user set header_url = #{headerUrl} where id = #{id} 44 | 45 | 46 | 47 | update user set password = #{password} where id = #{id} 48 | 49 | 50 | -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/community/service/MessageService.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.community.service; 2 | 3 | import com.nowcoder.community.dao.MessageMapper; 4 | import com.nowcoder.community.entity.Message; 5 | import com.nowcoder.community.utils.SensitiveFilter; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.stereotype.Service; 8 | import org.springframework.web.util.HtmlUtils; 9 | 10 | import java.util.List; 11 | 12 | @Service 13 | public class MessageService { 14 | 15 | @Autowired 16 | private MessageMapper messageMapper; 17 | 18 | @Autowired 19 | private SensitiveFilter sensitiveFilter; 20 | 21 | public List findConversations(int userId, int offset, int limit) { 22 | return messageMapper.selectConversations(userId, offset, limit); 23 | } 24 | 25 | public int findConversationCount(int userId) { 26 | return messageMapper.selectConversationCount(userId); 27 | } 28 | 29 | public List findLetters(String conversationId, int offset, int limit) { 30 | return messageMapper.selectLetters(conversationId, offset, limit); 31 | } 32 | 33 | public int findLetterCount(String conversationId) { 34 | return messageMapper.selectLetterCount(conversationId); 35 | } 36 | 37 | public int findLetterUnreadCount(int userId, String conversationId) { 38 | return messageMapper.selectLetterUnreadCount(userId, conversationId); 39 | } 40 | 41 | public int addMessage(Message message) { 42 | message.setContent(HtmlUtils.htmlEscape(message.getContent())); 43 | message.setContent(sensitiveFilter.filter(message.getContent())); 44 | return messageMapper.insertMessage(message); 45 | } 46 | 47 | public int readMessage(List ids) { 48 | return messageMapper.updateStatus(ids, 1); 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/community/utils/RedisKeyUtil.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.community.utils; 2 | 3 | public class RedisKeyUtil { 4 | 5 | private static final String SPLIT = ":"; 6 | private static final String PREFIX_ENTITY_LIKE = "like:entity"; 7 | private static final String PREFIX_USER_LIKE = "like:user"; 8 | private static final String PREFIX_FOLLOWEE = "followee"; 9 | private static final String PREFIX_FOLLOWER = "follower"; 10 | private static final String PREFIX_KAPTCHA = "kaptcha"; 11 | private static final String PREFIX_TICKET = "ticket"; 12 | private static final String PREFIX_USER = "user"; 13 | 14 | //某个实体的赞 15 | //like:entity:entityType:entityId->set(userId) 16 | public static String getEntityLikeKey(int entityType, int entityId){ 17 | return PREFIX_ENTITY_LIKE+SPLIT+entityType+SPLIT+entityId; 18 | } 19 | 20 | //某个用户的赞 21 | public static String getUserLikeKey(int userId){ 22 | return PREFIX_USER_LIKE+SPLIT+userId; 23 | } 24 | 25 | 26 | //某个用户关注的实体 27 | //followee:userId:entityType->zset(entityId,nowTime) 28 | public static String getFolloweeKey(int userId, int entityType){ 29 | return PREFIX_FOLLOWEE+SPLIT+userId+SPLIT+entityType; 30 | } 31 | 32 | //某个用户拥有的粉丝 33 | //follower:entityType:entityId->zset(userId,nowTime) 34 | public static String getFollowerKey(int entityType, int entityId){ 35 | return PREFIX_FOLLOWER+SPLIT+entityType+SPLIT+entityId; 36 | } 37 | 38 | //验证码key 39 | public static String getKaptchaKey(String owner){ 40 | return PREFIX_KAPTCHA+SPLIT+owner; 41 | } 42 | 43 | //登录凭证key 44 | public static String getTicketKey(String ticket){ 45 | return PREFIX_TICKET+SPLIT+ticket; 46 | } 47 | 48 | //用户key 49 | public static String getUserKey(int userId){ 50 | return PREFIX_USER+SPLIT+userId; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/community/entity/Page.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.community.entity; 2 | 3 | /** 4 | * 封装分页相关的信息. 5 | */ 6 | public class Page { 7 | 8 | // 当前页码 9 | private int current = 1; 10 | // 显示上限 11 | private int limit = 10; 12 | // 数据总数(用于计算总页数) 13 | private int rows; 14 | // 查询路径(用于复用分页链接) 15 | private String path; 16 | 17 | public int getCurrent() { 18 | return current; 19 | } 20 | 21 | public void setCurrent(int current) { 22 | if (current >= 1) { 23 | this.current = current; 24 | } 25 | } 26 | 27 | public int getLimit() { 28 | return limit; 29 | } 30 | 31 | public void setLimit(int limit) { 32 | if (limit >= 1 && limit <= 100) { 33 | this.limit = limit; 34 | } 35 | } 36 | 37 | public int getRows() { 38 | return rows; 39 | } 40 | 41 | public void setRows(int rows) { 42 | if (rows >= 0) { 43 | this.rows = rows; 44 | } 45 | } 46 | 47 | public String getPath() { 48 | return path; 49 | } 50 | 51 | public void setPath(String path) { 52 | this.path = path; 53 | } 54 | 55 | /** 56 | * 获取当前页的起始行 57 | * 58 | * @return 59 | */ 60 | public int getOffset() { 61 | // current * limit - limit 62 | return (current - 1) * limit; 63 | } 64 | 65 | /** 66 | * 获取总页数 67 | * 68 | * @return 69 | */ 70 | public int getTotal() { 71 | // rows / limit [+1] 72 | if (rows % limit == 0) { 73 | return rows / limit; 74 | } else { 75 | return rows / limit + 1; 76 | } 77 | } 78 | 79 | /** 80 | * 获取起始页码 81 | * 82 | * @return 83 | */ 84 | public int getFrom() { 85 | int from = current - 2; 86 | return from < 1 ? 1 : from; 87 | } 88 | 89 | /** 90 | * 获取结束页码 91 | * 92 | * @return 93 | */ 94 | public int getTo() { 95 | int to = current + 2; 96 | int total = getTotal(); 97 | return to > total ? total : to; 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /src/main/resources/static/css/global.css: -------------------------------------------------------------------------------- 1 | html { 2 | height: 100%; 3 | } 4 | 5 | body { 6 | background: #eee; 7 | font-family: arial, STHeiti, 'Microsoft YaHei', \5b8b\4f53; 8 | font-size: 14px; 9 | height: 100%; 10 | } 11 | 12 | .nk-container { 13 | position: relative; 14 | height: auto; 15 | min-height: 100%; 16 | } 17 | 18 | .container { 19 | width: 960px; 20 | padding: 0; 21 | } 22 | 23 | header .navbar-brand { 24 | background: url('http://static.nowcoder.com/images/res/logo/logo-v3.png') no-repeat; 25 | background-size: 147px 42px; 26 | width: 147px; 27 | height: 42px; 28 | margin: 5px 15px 5px 0; 29 | } 30 | 31 | header .navbar { 32 | padding: 5px 0; 33 | font-size: 16px; 34 | } 35 | 36 | header .badge { 37 | position: absolute; 38 | top: -3px; 39 | left: 33px; 40 | } 41 | 42 | footer { 43 | padding: 20px 0; 44 | font-size: 12px; 45 | position: absolute; 46 | bottom: 0; 47 | width: 100%; 48 | } 49 | 50 | footer .qrcode { 51 | text-align: center; 52 | } 53 | 54 | footer .detail-info{ 55 | border-left: 1px solid #888; 56 | } 57 | 58 | footer .company-info li { 59 | padding-left: 16px; 60 | margin: 4px 0; 61 | } 62 | 63 | .main { 64 | padding: 20px 0; 65 | padding-bottom: 200px; 66 | } 67 | 68 | .main .container { 69 | background: #fff; 70 | padding: 20px; 71 | } 72 | 73 | i { 74 | font-style: normal; 75 | } 76 | 77 | u { 78 | text-decoration: none; 79 | } 80 | 81 | b { 82 | font-weight: normal; 83 | } 84 | 85 | a { 86 | color: #000; 87 | } 88 | 89 | a:hover { 90 | text-decoration: none; 91 | } 92 | 93 | .font-size-12 { 94 | font-size: 12px; 95 | } 96 | .font-size-14 { 97 | font-size: 14px; 98 | } 99 | .font-size-16 { 100 | font-size: 16px; 101 | } 102 | .font-size-18 { 103 | font-size: 18px; 104 | } 105 | .font-size-20 { 106 | font-size: 20px; 107 | } 108 | .font-size-22 { 109 | font-size: 20px; 110 | } 111 | .font-size-24 { 112 | font-size: 20px; 113 | } 114 | 115 | .hidden { 116 | display: none; 117 | } 118 | 119 | .rt-0 { 120 | right: 0; 121 | top: 0; 122 | } 123 | 124 | .square { 125 | display: inline-block; 126 | width: 7px; 127 | height: 7px; 128 | background: #ff6547; 129 | margin-bottom: 2px; 130 | margin-right: 3px; 131 | } 132 | 133 | .bg-gray { 134 | background: #eff0f2; 135 | } 136 | 137 | .user-header { 138 | width: 50px; 139 | height: 50px; 140 | } 141 | 142 | em { 143 | font-style: normal; 144 | color: red; 145 | } 146 | -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/community/controller/HomeController.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.community.controller; 2 | 3 | import com.nowcoder.community.entity.DiscussPost; 4 | import com.nowcoder.community.entity.Page; 5 | import com.nowcoder.community.entity.User; 6 | import com.nowcoder.community.service.DiscussPostService; 7 | import com.nowcoder.community.service.LikeService; 8 | import com.nowcoder.community.service.UserService; 9 | import com.nowcoder.community.utils.CommunityConstant; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.stereotype.Controller; 12 | import org.springframework.ui.Model; 13 | import org.springframework.web.bind.annotation.RequestMapping; 14 | import org.springframework.web.bind.annotation.RequestMethod; 15 | 16 | import java.util.ArrayList; 17 | import java.util.HashMap; 18 | import java.util.List; 19 | import java.util.Map; 20 | 21 | @Controller 22 | public class HomeController implements CommunityConstant { 23 | 24 | @Autowired 25 | private DiscussPostService discussPostService; 26 | 27 | @Autowired 28 | private UserService userService; 29 | 30 | @Autowired 31 | private LikeService likeService; 32 | 33 | @RequestMapping(path = "/index", method = RequestMethod.GET) 34 | public String getIndexPage(Model model, Page page) { 35 | // 方法调用钱,SpringMVC会自动实例化Model和Page,并将Page注入Model. 36 | // 所以,在thymeleaf中可以直接访问Page对象中的数据. 37 | page.setRows(discussPostService.findDiscussPostRows(0)); 38 | page.setPath("/index"); 39 | 40 | List list = discussPostService.findDiscussPosts(0, page.getOffset(), page.getLimit()); 41 | List> discussPosts = new ArrayList<>(); 42 | if (list != null) { 43 | for (DiscussPost post : list) { 44 | Map map = new HashMap<>(); 45 | map.put("post", post); 46 | User user = userService.findUserById(post.getUserId()); 47 | map.put("user", user); 48 | 49 | //赞的数量 50 | long entityLikeCount = likeService.findEntityLikeCount(ENTITY_TYPE_POST, post.getId()); 51 | map.put("likeCount",entityLikeCount); 52 | discussPosts.add(map); 53 | } 54 | } 55 | model.addAttribute("discussPosts", discussPosts); 56 | return "/index"; 57 | } 58 | 59 | @RequestMapping(path = "/error", method = RequestMethod.GET) 60 | public String getErrorPage() { 61 | return "/error/500"; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/test/java/com/nowcoder/community/CommunityApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.community; 2 | 3 | import com.nowcoder.community.dao.AlphaDao; 4 | import com.nowcoder.community.service.AlphaService; 5 | import org.junit.Test; 6 | import org.junit.runner.RunWith; 7 | import org.springframework.beans.BeansException; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.beans.factory.annotation.Qualifier; 10 | import org.springframework.boot.test.context.SpringBootTest; 11 | import org.springframework.context.ApplicationContext; 12 | import org.springframework.context.ApplicationContextAware; 13 | import org.springframework.test.context.ContextConfiguration; 14 | import org.springframework.test.context.junit4.SpringRunner; 15 | 16 | import java.text.SimpleDateFormat; 17 | import java.util.Date; 18 | 19 | @RunWith(SpringRunner.class) 20 | @SpringBootTest 21 | @ContextConfiguration(classes = CommunityApplication.class) 22 | public class CommunityApplicationTests implements ApplicationContextAware { 23 | 24 | private ApplicationContext applicationContext; 25 | 26 | @Override 27 | public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { 28 | this.applicationContext = applicationContext; 29 | } 30 | 31 | @Test 32 | public void testApplicationContext() { 33 | System.out.println(applicationContext); 34 | 35 | AlphaDao alphaDao = applicationContext.getBean(AlphaDao.class); 36 | System.out.println(alphaDao.select()); 37 | 38 | alphaDao = applicationContext.getBean("alphaHibernate", AlphaDao.class); 39 | System.out.println(alphaDao.select()); 40 | } 41 | 42 | @Test 43 | public void testBeanManagement() { 44 | AlphaService alphaService = applicationContext.getBean(AlphaService.class); 45 | System.out.println(alphaService); 46 | 47 | alphaService = applicationContext.getBean(AlphaService.class); 48 | System.out.println(alphaService); 49 | } 50 | 51 | @Test 52 | public void testBeanConfig() { 53 | SimpleDateFormat simpleDateFormat = 54 | applicationContext.getBean(SimpleDateFormat.class); 55 | System.out.println(simpleDateFormat.format(new Date())); 56 | } 57 | 58 | @Autowired 59 | @Qualifier("alphaHibernate") 60 | private AlphaDao alphaDao; 61 | 62 | @Autowired 63 | private AlphaService alphaService; 64 | 65 | @Autowired 66 | private SimpleDateFormat simpleDateFormat; 67 | 68 | @Test 69 | public void testDI() { 70 | System.out.println(alphaDao); 71 | System.out.println(alphaService); 72 | System.out.println(simpleDateFormat); 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/community/service/LikeService.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.community.service; 2 | 3 | import com.nowcoder.community.utils.RedisKeyUtil; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.dao.DataAccessException; 6 | import org.springframework.data.redis.core.RedisOperations; 7 | import org.springframework.data.redis.core.RedisTemplate; 8 | import org.springframework.data.redis.core.SessionCallback; 9 | import org.springframework.stereotype.Service; 10 | 11 | @Service 12 | public class LikeService { 13 | 14 | @Autowired 15 | private RedisTemplate redisTemplate; 16 | 17 | //点赞 18 | public void like(int userId, int entityType, int entityId, int entityUserId){ 19 | 20 | redisTemplate.execute(new SessionCallback() { 21 | @Override 22 | public Object execute(RedisOperations redisOperations) throws DataAccessException { 23 | String entityLikeKey = RedisKeyUtil.getEntityLikeKey(entityType,entityId); 24 | String userLikeKey = RedisKeyUtil.getUserLikeKey(entityUserId); 25 | Boolean Ismember = redisOperations.opsForSet().isMember(entityLikeKey, userId); 26 | redisOperations.multi(); //开启事务 27 | 28 | if(Ismember){ 29 | redisOperations.opsForSet().remove(entityLikeKey, userId); 30 | redisOperations.opsForValue().decrement(userLikeKey); 31 | }else{ 32 | redisOperations.opsForSet().add(entityLikeKey, userId); 33 | redisOperations.opsForValue().increment(userLikeKey); 34 | } 35 | 36 | return redisOperations.exec(); //执行事务 37 | } 38 | }); 39 | } 40 | 41 | //查询某实体点赞数量 42 | public long findEntityLikeCount(int entityType, int entityId){ 43 | String entityLikeKey = RedisKeyUtil.getEntityLikeKey(entityType, entityId); 44 | return redisTemplate.opsForSet().size(entityLikeKey); 45 | } 46 | 47 | //查询某人对某实体的点赞状态 48 | public int findEntityLikeStatus(int userId, int entityType, int entityId){ 49 | String entityLikeKey = RedisKeyUtil.getEntityLikeKey(entityType, entityId); 50 | return redisTemplate.opsForSet().isMember(entityLikeKey,userId)?1:0; 51 | } 52 | 53 | //查询某个用户获得的赞总数 54 | public int findUserLikeCount(int userId){ 55 | String userLikeKey = RedisKeyUtil.getUserLikeKey(userId); 56 | Integer count = (Integer) redisTemplate.opsForValue().get(userLikeKey); 57 | return count == null ? 0 : count.intValue(); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/community/entity/DiscussPost.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.community.entity; 2 | 3 | import java.util.Date; 4 | 5 | public class DiscussPost { 6 | 7 | private int id; 8 | private int userId; 9 | private String title; 10 | private String content; 11 | private int type; 12 | private int status; 13 | private Date createTime; 14 | private int commentCount; 15 | private double score; 16 | 17 | public int getId() { 18 | return id; 19 | } 20 | 21 | public void setId(int id) { 22 | this.id = id; 23 | } 24 | 25 | public int getUserId() { 26 | return userId; 27 | } 28 | 29 | public void setUserId(int userId) { 30 | this.userId = userId; 31 | } 32 | 33 | public String getTitle() { 34 | return title; 35 | } 36 | 37 | public void setTitle(String title) { 38 | this.title = title; 39 | } 40 | 41 | public String getContent() { 42 | return content; 43 | } 44 | 45 | public void setContent(String content) { 46 | this.content = content; 47 | } 48 | 49 | public int getType() { 50 | return type; 51 | } 52 | 53 | public void setType(int type) { 54 | this.type = type; 55 | } 56 | 57 | public int getStatus() { 58 | return status; 59 | } 60 | 61 | public void setStatus(int status) { 62 | this.status = status; 63 | } 64 | 65 | public Date getCreateTime() { 66 | return createTime; 67 | } 68 | 69 | public void setCreateTime(Date createTime) { 70 | this.createTime = createTime; 71 | } 72 | 73 | public int getCommentCount() { 74 | return commentCount; 75 | } 76 | 77 | public void setCommentCount(int commentCount) { 78 | this.commentCount = commentCount; 79 | } 80 | 81 | public double getScore() { 82 | return score; 83 | } 84 | 85 | public void setScore(double score) { 86 | this.score = score; 87 | } 88 | 89 | @Override 90 | public String toString() { 91 | return "DiscussPost{" + 92 | "id=" + id + 93 | ", userId=" + userId + 94 | ", title='" + title + '\'' + 95 | ", content='" + content + '\'' + 96 | ", type=" + type + 97 | ", status=" + status + 98 | ", createTime=" + createTime + 99 | ", commentCount=" + commentCount + 100 | ", score=" + score + 101 | '}'; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/main/resources/mapper/message-mapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | id, from_id, to_id, conversation_id, content, status, create_time 9 | 10 | 11 | 12 | from_id, to_id, conversation_id, content, status, create_time 13 | 14 | 15 | 28 | 29 | 38 | 39 | 48 | 49 | 56 | 57 | 67 | 68 | 69 | insert into message() 70 | values(#{fromId},#{toId},#{conversationId},#{content},#{status},#{createTime}) 71 | 72 | 73 | 74 | update message set status = #{status} 75 | where id in 76 | 77 | #{id} 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/community/config/interceptor/LoginTicketInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.community.config.interceptor; 2 | 3 | import com.nowcoder.community.entity.LoginTicket; 4 | import com.nowcoder.community.entity.User; 5 | import com.nowcoder.community.service.UserService; 6 | import com.nowcoder.community.utils.CookieUtil; 7 | import com.nowcoder.community.utils.HostHolder; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.stereotype.Component; 10 | import org.springframework.web.servlet.HandlerInterceptor; 11 | import org.springframework.web.servlet.ModelAndView; 12 | 13 | import javax.servlet.http.HttpServletRequest; 14 | import javax.servlet.http.HttpServletResponse; 15 | import java.util.Date; 16 | 17 | @Component 18 | public class LoginTicketInterceptor implements HandlerInterceptor { 19 | 20 | @Autowired 21 | private UserService userService; 22 | 23 | @Autowired 24 | private HostHolder hostHolder; 25 | 26 | /** 27 | * Controller执行前拦截 28 | * @param request 29 | * @param response 30 | * @param handler 31 | * @return 32 | * @throws Exception 33 | */ 34 | @Override 35 | public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { 36 | 37 | String ticket = CookieUtil.getValue(request, "ticket"); 38 | if(ticket != null){ 39 | //查询凭证 40 | LoginTicket loginTicket = userService.findLoginTicket(ticket); 41 | //检查凭证是否有效 42 | if(loginTicket != null && loginTicket.getStatus() == 0 && loginTicket.getExpired().after(new Date())){ 43 | //根据凭证查询用户 44 | User user = userService.findUserById(loginTicket.getUserId()); 45 | //在本次请求中持有用户,将用户信息存入当前线程 46 | hostHolder.setUser(user); 47 | } 48 | } 49 | return true; 50 | } 51 | 52 | /** 53 | * Controller执行后,将用户信息加入视图层 54 | * @param request 55 | * @param response 56 | * @param handler 57 | * @param modelAndView 58 | * @throws Exception 59 | */ 60 | @Override 61 | public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { 62 | User user = hostHolder.getUser(); 63 | if(user != null && modelAndView != null){ 64 | modelAndView.addObject("loginUser",user); 65 | 66 | } 67 | } 68 | 69 | /** 70 | * 程序运行结束后 71 | * @param request 72 | * @param response 73 | * @param handler 74 | * @param ex 75 | * @throws Exception 76 | */ 77 | @Override 78 | public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { 79 | hostHolder.clear(); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/community/entity/User.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.community.entity; 2 | 3 | import java.util.Date; 4 | 5 | public class User { 6 | 7 | private int id; 8 | private String username; 9 | private String password; 10 | private String salt; //密码盐值 11 | private String email; 12 | private int type; 13 | private int status; //是否激活 14 | private String activationCode; //激活码 15 | private String headerUrl; 16 | private Date createTime; 17 | 18 | public int getId() { 19 | return id; 20 | } 21 | 22 | public void setId(int id) { 23 | this.id = id; 24 | } 25 | 26 | public String getUsername() { 27 | return username; 28 | } 29 | 30 | public void setUsername(String username) { 31 | this.username = username; 32 | } 33 | 34 | public String getPassword() { 35 | return password; 36 | } 37 | 38 | public void setPassword(String password) { 39 | this.password = password; 40 | } 41 | 42 | public String getSalt() { 43 | return salt; 44 | } 45 | 46 | public void setSalt(String salt) { 47 | this.salt = salt; 48 | } 49 | 50 | public String getEmail() { 51 | return email; 52 | } 53 | 54 | public void setEmail(String email) { 55 | this.email = email; 56 | } 57 | 58 | public int getType() { 59 | return type; 60 | } 61 | 62 | public void setType(int type) { 63 | this.type = type; 64 | } 65 | 66 | public int getStatus() { 67 | return status; 68 | } 69 | 70 | public void setStatus(int status) { 71 | this.status = status; 72 | } 73 | 74 | public String getActivationCode() { 75 | return activationCode; 76 | } 77 | 78 | public void setActivationCode(String activationCode) { 79 | this.activationCode = activationCode; 80 | } 81 | 82 | public String getHeaderUrl() { 83 | return headerUrl; 84 | } 85 | 86 | public void setHeaderUrl(String headerUrl) { 87 | this.headerUrl = headerUrl; 88 | } 89 | 90 | public Date getCreateTime() { 91 | return createTime; 92 | } 93 | 94 | public void setCreateTime(Date createTime) { 95 | this.createTime = createTime; 96 | } 97 | 98 | @Override 99 | public String toString() { 100 | return "User{" + 101 | "id=" + id + 102 | ", username='" + username + '\'' + 103 | ", password='" + password + '\'' + 104 | ", salt='" + salt + '\'' + 105 | ", email='" + email + '\'' + 106 | ", type=" + type + 107 | ", status=" + status + 108 | ", activationCode='" + activationCode + '\'' + 109 | ", headerUrl='" + headerUrl + '\'' + 110 | ", createTime=" + createTime + 111 | '}'; 112 | } 113 | 114 | } 115 | -------------------------------------------------------------------------------- /src/test/java/com/nowcoder/community/MapperTests.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.community; 2 | 3 | import com.nowcoder.community.dao.DiscussPostMapper; 4 | import com.nowcoder.community.dao.LoginTicketMapper; 5 | import com.nowcoder.community.dao.UserMapper; 6 | import com.nowcoder.community.entity.DiscussPost; 7 | import com.nowcoder.community.entity.LoginTicket; 8 | import com.nowcoder.community.entity.User; 9 | import org.junit.Test; 10 | import org.junit.runner.RunWith; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.boot.test.context.SpringBootTest; 13 | import org.springframework.test.context.ContextConfiguration; 14 | import org.springframework.test.context.junit4.SpringRunner; 15 | 16 | import java.util.Date; 17 | import java.util.List; 18 | 19 | @RunWith(SpringRunner.class) 20 | @SpringBootTest 21 | @ContextConfiguration(classes = CommunityApplication.class) 22 | public class MapperTests { 23 | 24 | @Autowired 25 | private UserMapper userMapper; 26 | 27 | @Autowired 28 | private DiscussPostMapper discussPostMapper; 29 | 30 | @Autowired 31 | private LoginTicketMapper loginTicketMapper; 32 | 33 | @Test 34 | public void testSelectUser() { 35 | User user = userMapper.selectById(101); 36 | System.out.println(user); 37 | 38 | user = userMapper.selectByName("liubei"); 39 | System.out.println(user); 40 | 41 | user = userMapper.selectByEmail("nowcoder101@sina.com"); 42 | System.out.println(user); 43 | } 44 | 45 | @Test 46 | public void testInsertUser() { 47 | User user = new User(); 48 | user.setUsername("test"); 49 | user.setPassword("123456"); 50 | user.setSalt("abc"); 51 | user.setEmail("test@qq.com"); 52 | user.setHeaderUrl("http://www.nowcoder.com/101.png"); 53 | user.setCreateTime(new Date()); 54 | 55 | int rows = userMapper.insertUser(user); 56 | System.out.println(rows); 57 | System.out.println(user.getId()); 58 | } 59 | 60 | @Test 61 | public void updateUser() { 62 | int rows = userMapper.updateStatus(150, 1); 63 | System.out.println(rows); 64 | 65 | rows = userMapper.updateHeader(150, "http://www.nowcoder.com/102.png"); 66 | System.out.println(rows); 67 | 68 | rows = userMapper.updatePassword(150, "hello"); 69 | System.out.println(rows); 70 | } 71 | 72 | @Test 73 | public void testSelectPosts() { 74 | List list = discussPostMapper.selectDiscussPosts(149, 0, 10); 75 | for(DiscussPost post : list) { 76 | System.out.println(post); 77 | } 78 | 79 | int rows = discussPostMapper.selectDiscussPostRows(149); 80 | System.out.println(rows); 81 | } 82 | 83 | @Test 84 | public void testLoginTicket(){ 85 | LoginTicket loginTicket = new LoginTicket(); 86 | loginTicket.setUserId(10); 87 | loginTicket.setTicket("Abc"); 88 | loginTicket.setStatus(0); 89 | loginTicket.setExpired(new Date(System.currentTimeMillis()+1000*60*10)); 90 | 91 | loginTicketMapper.insertLoinTicket(loginTicket); 92 | } 93 | 94 | @Test 95 | public void testSelectLoginTicket(){ 96 | 97 | LoginTicket abc = loginTicketMapper.selectByTicket("abc"); 98 | System.out.println(abc); 99 | 100 | loginTicketMapper.updateStatus("abc",1); 101 | abc = loginTicketMapper.selectByTicket("abc"); 102 | System.out.println(abc); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.1.5.RELEASE 9 | 10 | 11 | com.nowcoder.community 12 | community 13 | 0.0.1-SNAPSHOT 14 | community 15 | nowcoder community 16 | 17 | 18 | 12 19 | 20 | 21 | 22 | 23 | org.springframework.boot 24 | spring-boot-starter-aop 25 | 26 | 27 | org.springframework.boot 28 | spring-boot-starter-thymeleaf 29 | 30 | 31 | org.springframework.boot 32 | spring-boot-starter-web 33 | 34 | 35 | 36 | org.springframework.boot 37 | spring-boot-devtools 38 | runtime 39 | 40 | 41 | org.springframework.boot 42 | spring-boot-starter-test 43 | test 44 | 45 | 46 | 47 | mysql 48 | mysql-connector-java 49 | 8.0.16 50 | 51 | 52 | org.mybatis.spring.boot 53 | mybatis-spring-boot-starter 54 | 2.0.1 55 | 56 | 57 | org.projectlombok 58 | lombok 59 | provided 60 | 61 | 62 | 63 | 64 | org.springframework.boot 65 | spring-boot-starter-mail 66 | 2.2.6.RELEASE 67 | 68 | 69 | 70 | 71 | org.apache.commons 72 | commons-lang3 73 | 3.10 74 | 75 | 76 | 77 | 78 | 79 | com.github.penggle 80 | kaptcha 81 | 2.3.2 82 | 83 | 84 | 85 | 86 | com.alibaba 87 | fastjson 88 | 1.2.68 89 | 90 | 91 | 92 | 93 | org.springframework.boot 94 | spring-boot-starter-data-redis 95 | 2.2.6.RELEASE 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | org.springframework.boot 105 | spring-boot-maven-plugin 106 | 107 | 108 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /src/main/resources/templates/site/operate-result.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 牛客网-操作结果 10 | 11 | 12 |
13 | 14 |
15 | 16 | 17 |
18 |
19 |
20 |

21 |
22 |

23 | 系统会在 8 秒后自动跳转, 24 | 您也可以点此 链接, 手动跳转! 25 |

26 |
27 |
28 |
29 | 30 | 31 |
32 |
33 |
34 | 35 |
36 | 37 |
38 | 39 |
40 |
41 |
42 | 65 |
66 |
67 |
68 |
69 | 85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 | 93 | 94 | 95 | 96 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /src/main/resources/logback-spring.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | community 4 | 5 | 6 | 7 | 8 | 9 | ${LOG_PATH}/${APPDIR}/log_error.log 10 | 11 | ${LOG_PATH}/${APPDIR}/error/log-error-%d{yyyy-MM-dd}.%i.log 12 | 13 | 5MB 14 | 15 | 30 16 | 17 | true 18 | 19 | %d %level [%thread] %logger{10} [%file:%line] %msg%n 20 | utf-8 21 | 22 | 23 | error 24 | ACCEPT 25 | DENY 26 | 27 | 28 | 29 | 30 | 31 | ${LOG_PATH}/${APPDIR}/log_warn.log 32 | 33 | ${LOG_PATH}/${APPDIR}/warn/log-warn-%d{yyyy-MM-dd}.%i.log 34 | 35 | 5MB 36 | 37 | 30 38 | 39 | true 40 | 41 | %d %level [%thread] %logger{10} [%file:%line] %msg%n 42 | utf-8 43 | 44 | 45 | warn 46 | ACCEPT 47 | DENY 48 | 49 | 50 | 51 | 52 | 53 | ${LOG_PATH}/${APPDIR}/log_info.log 54 | 55 | ${LOG_PATH}/${APPDIR}/info/log-info-%d{yyyy-MM-dd}.%i.log 56 | 57 | 5MB 58 | 59 | 30 60 | 61 | true 62 | 63 | %d %level [%thread] %logger{10} [%file:%line] %msg%n 64 | utf-8 65 | 66 | 67 | info 68 | ACCEPT 69 | DENY 70 | 71 | 72 | 73 | 74 | 75 | 76 | %d %level [%thread] %logger{10} [%file:%line] %msg%n 77 | utf-8 78 | 79 | 80 | debug 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/community/utils/SensitiveFilter.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.community.utils; 2 | 3 | import org.apache.commons.lang3.CharUtils; 4 | import org.apache.commons.lang3.StringUtils; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.springframework.stereotype.Component; 8 | 9 | import javax.annotation.PostConstruct; 10 | import java.io.BufferedReader; 11 | import java.io.IOException; 12 | import java.io.InputStream; 13 | import java.io.InputStreamReader; 14 | import java.util.HashMap; 15 | import java.util.Map; 16 | 17 | @Component 18 | public class SensitiveFilter { 19 | 20 | private static final Logger LOGGER = LoggerFactory.getLogger(SensitiveFilter.class); 21 | 22 | private static final String REPLACEMENT = "***"; 23 | 24 | //根节点 25 | private TrieNode rootNode = new TrieNode(); 26 | 27 | //初始化字典树 28 | @PostConstruct 29 | public void init(){ 30 | try(InputStream is = this.getClass().getClassLoader().getResourceAsStream("sensitive-words.txt");//从类目录下加载文件资源 31 | BufferedReader reader = new BufferedReader(new InputStreamReader(is));){ 32 | 33 | String keyword; 34 | while((keyword=reader.readLine())!=null){ 35 | this.addKeyWord(keyword); 36 | } 37 | 38 | }catch (IOException e){ 39 | 40 | } 41 | } 42 | 43 | //添加敏感词,字典树插入操作 44 | private void addKeyWord(String keyword){ 45 | TrieNode node = rootNode; 46 | for(int i = 0; i < keyword.length(); i++){ 47 | char c = keyword.charAt(i); 48 | TrieNode subNode = node.getSubNode(c); 49 | if(subNode == null){ 50 | subNode = new TrieNode(); 51 | node.addSubNode(c,subNode); 52 | } 53 | node = subNode; 54 | if(i == keyword.length()-1){ 55 | node.setKeywordEnd(true); //设置结束位置为敏感词 56 | } 57 | } 58 | } 59 | 60 | /** 61 | * 过滤敏感词 62 | * @param text 待过滤文本 63 | * @return 64 | */ 65 | public String filter(String text){ 66 | if(StringUtils.isBlank(text)){ 67 | return null; 68 | } 69 | 70 | //指针1 71 | TrieNode tmpNode = rootNode; 72 | //指针2 73 | int begin = 0; 74 | //指针3 75 | int position = 0; 76 | //结果 77 | StringBuilder ans = new StringBuilder(""); 78 | int len = text.length(); 79 | while(position < len){ 80 | char c = text.charAt(position); 81 | //跳过符号 82 | if(isSymbol(c)){ 83 | if(tmpNode == rootNode){ 84 | ans.append(c); 85 | begin++; 86 | } 87 | position++; 88 | }else{ 89 | tmpNode = tmpNode.getSubNode(c); 90 | if(tmpNode == null){ //不是敏感词 91 | ans.append(text.charAt(begin)); 92 | position = ++begin; 93 | tmpNode = rootNode; 94 | }else if(tmpNode.isKeywordEnd()){ //是敏感词 95 | ans.append(REPLACEMENT); 96 | begin = ++position; 97 | }else{ 98 | position++; 99 | } 100 | } 101 | } 102 | 103 | //将最后一批结果插入 104 | ans.append(text.substring(begin)); 105 | return ans.toString(); 106 | } 107 | 108 | //判断是否为符号 109 | private boolean isSymbol(char c){ 110 | //0x9fff~0x2e80 东亚文字范围 111 | return !CharUtils.isAsciiAlphanumeric(c) && (c < 0x2E80 || c > 0x9FFF); 112 | } 113 | /** 114 | * 字典树 115 | */ 116 | private class TrieNode{ 117 | 118 | //关键字结束标识 119 | private boolean isKeywordEnd = false; 120 | 121 | //子节点 122 | private Map subNodes = new HashMap<>(); 123 | 124 | public boolean isKeywordEnd() { 125 | return isKeywordEnd; 126 | } 127 | 128 | public void setKeywordEnd(boolean keywordEnd) { 129 | isKeywordEnd = keywordEnd; 130 | } 131 | 132 | //添加子节点 133 | public void addSubNode(Character c, TrieNode node){ 134 | subNodes.put(c,node); 135 | } 136 | 137 | //获取子节点 138 | public TrieNode getSubNode(Character c){ 139 | return subNodes.get(c); 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/community/controller/FollowController.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.community.controller; 2 | 3 | import com.nowcoder.community.annotation.LoginRequired; 4 | import com.nowcoder.community.entity.Page; 5 | import com.nowcoder.community.entity.User; 6 | import com.nowcoder.community.service.FollowService; 7 | import com.nowcoder.community.service.UserService; 8 | import com.nowcoder.community.utils.CommunityConstant; 9 | import com.nowcoder.community.utils.CommunityUtil; 10 | import com.nowcoder.community.utils.HostHolder; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.stereotype.Controller; 13 | import org.springframework.ui.Model; 14 | import org.springframework.web.bind.annotation.PathVariable; 15 | import org.springframework.web.bind.annotation.RequestMapping; 16 | import org.springframework.web.bind.annotation.RequestMethod; 17 | import org.springframework.web.bind.annotation.ResponseBody; 18 | 19 | import java.util.List; 20 | import java.util.Map; 21 | 22 | /** 23 | * 粉丝关注相关 24 | */ 25 | @Controller 26 | public class FollowController implements CommunityConstant { 27 | 28 | @Autowired 29 | private FollowService followService; 30 | 31 | @Autowired 32 | private HostHolder hostHolder; 33 | 34 | @Autowired 35 | private UserService userService; 36 | 37 | @RequestMapping(path = "/follow",method = RequestMethod.POST) 38 | @ResponseBody 39 | @LoginRequired 40 | public String follow(int entityType, int entityId){ 41 | User user = hostHolder.getUser(); 42 | 43 | followService.follow(user.getId(), entityType, entityId); 44 | return CommunityUtil.getJsonString(0,"已关注"); 45 | } 46 | 47 | @RequestMapping(path = "/unfollow",method = RequestMethod.POST) 48 | @ResponseBody 49 | @LoginRequired 50 | public String unfollow(int entityType, int entityId){ 51 | User user = hostHolder.getUser(); 52 | 53 | followService.unfollow(user.getId(), entityType, entityId); 54 | return CommunityUtil.getJsonString(0,"已取消关注"); 55 | } 56 | 57 | /** 58 | * 获取关注的人列表 59 | * @param userId 60 | * @param page 61 | * @param model 62 | * @return 63 | */ 64 | @RequestMapping(path = "/followees/{userId}", method = RequestMethod.GET) 65 | public String getFollowees(@PathVariable("userId") int userId, Page page, Model model) { 66 | User user = userService.findUserById(userId); 67 | if (user == null) { 68 | throw new RuntimeException("该用户不存在!"); 69 | } 70 | model.addAttribute("user", user); 71 | 72 | page.setLimit(5); 73 | page.setPath("/followees/" + userId); 74 | page.setRows((int) followService.findFolloweeCount(userId, ENTITY_TYPE_USER)); 75 | 76 | List> userList = followService.findFollowees(userId, page.getOffset(), page.getLimit()); 77 | if (userList != null) { 78 | for (Map map : userList) { 79 | User u = (User) map.get("user"); 80 | map.put("hasFollowed", hasFollowed(u.getId())); 81 | } 82 | } 83 | model.addAttribute("users", userList); 84 | 85 | return "/site/followee"; 86 | } 87 | 88 | /** 89 | * 获取粉丝列表 90 | * @param userId 91 | * @param page 92 | * @param model 93 | * @return 94 | */ 95 | @RequestMapping(path = "/followers/{userId}", method = RequestMethod.GET) 96 | public String getFollowers(@PathVariable("userId") int userId, Page page, Model model) { 97 | User user = userService.findUserById(userId); 98 | if (user == null) { 99 | throw new RuntimeException("该用户不存在!"); 100 | } 101 | model.addAttribute("user", user); 102 | 103 | page.setLimit(5); 104 | page.setPath("/followers/" + userId); 105 | page.setRows((int) followService.findFollowerCount(ENTITY_TYPE_USER, userId)); 106 | 107 | List> userList = followService.findFollowers(userId, page.getOffset(), page.getLimit()); 108 | if (userList != null) { 109 | for (Map map : userList) { 110 | User u = (User) map.get("user"); 111 | map.put("hasFollowed", hasFollowed(u.getId())); 112 | } 113 | } 114 | model.addAttribute("users", userList); 115 | 116 | return "/site/follower"; 117 | } 118 | 119 | private boolean hasFollowed(int userId) { 120 | if (hostHolder.getUser() == null) { 121 | return false; 122 | } 123 | 124 | return followService.hasFollowed(hostHolder.getUser().getId(), ENTITY_TYPE_USER, userId); 125 | } 126 | 127 | } 128 | -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/community/controller/AlphaController.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.community.controller; 2 | 3 | import com.nowcoder.community.service.AlphaService; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.stereotype.Controller; 6 | import org.springframework.ui.Model; 7 | import org.springframework.web.bind.annotation.*; 8 | import org.springframework.web.servlet.ModelAndView; 9 | 10 | import javax.servlet.http.HttpServletRequest; 11 | import javax.servlet.http.HttpServletResponse; 12 | import java.io.IOException; 13 | import java.io.PrintWriter; 14 | import java.util.*; 15 | 16 | @Controller 17 | @RequestMapping("/alpha") 18 | public class AlphaController { 19 | 20 | @Autowired 21 | private AlphaService alphaService; 22 | 23 | @RequestMapping("/hello") 24 | @ResponseBody 25 | public String sayHello() { 26 | return "Hello Spring Boot."; 27 | } 28 | 29 | @RequestMapping("/data") 30 | @ResponseBody 31 | public String getData() { 32 | return alphaService.find(); 33 | } 34 | 35 | @RequestMapping("/http") 36 | public void http(HttpServletRequest request, HttpServletResponse response) { 37 | // 获取请求数据 38 | System.out.println(request.getMethod()); 39 | System.out.println(request.getServletPath()); 40 | Enumeration enumeration = request.getHeaderNames(); 41 | while (enumeration.hasMoreElements()) { 42 | String name = enumeration.nextElement(); 43 | String value = request.getHeader(name); 44 | System.out.println(name + ": " + value); 45 | } 46 | System.out.println(request.getParameter("code")); 47 | 48 | // 返回响应数据 49 | response.setContentType("text/html;charset=utf-8"); 50 | try ( 51 | PrintWriter writer = response.getWriter(); 52 | ) { 53 | writer.write("

牛客网

"); 54 | } catch (IOException e) { 55 | e.printStackTrace(); 56 | } 57 | } 58 | 59 | // GET请求 60 | 61 | // /students?current=1&limit=20 62 | @RequestMapping(path = "/students", method = RequestMethod.GET) 63 | @ResponseBody 64 | public String getStudents( 65 | @RequestParam(name = "current", required = false, defaultValue = "1") int current, 66 | @RequestParam(name = "limit", required = false, defaultValue = "10") int limit) { 67 | System.out.println(current); 68 | System.out.println(limit); 69 | return "some students"; 70 | } 71 | 72 | // /student/123 73 | @RequestMapping(path = "/student/{id}", method = RequestMethod.GET) 74 | @ResponseBody 75 | public String getStudent(@PathVariable("id") int id) { 76 | System.out.println(id); 77 | return "a student"; 78 | } 79 | 80 | // POST请求 81 | @RequestMapping(path = "/student", method = RequestMethod.POST) 82 | @ResponseBody 83 | public String saveStudent(String name, int age) { 84 | System.out.println(name); 85 | System.out.println(age); 86 | return "success"; 87 | } 88 | 89 | // 响应HTML数据 90 | 91 | @RequestMapping(path = "/teacher", method = RequestMethod.GET) 92 | public ModelAndView getTeacher() { 93 | ModelAndView mav = new ModelAndView(); 94 | mav.addObject("name", "张三"); 95 | mav.addObject("age", 30); 96 | mav.setViewName("/demo/view"); 97 | return mav; 98 | } 99 | 100 | @RequestMapping(path = "/school", method = RequestMethod.GET) 101 | public String getSchool(Model model) { 102 | model.addAttribute("name", "北京大学"); 103 | model.addAttribute("age", 80); 104 | return "/demo/view"; 105 | } 106 | 107 | // 响应JSON数据(异步请求) 108 | // Java对象 -> JSON字符串 -> JS对象 109 | 110 | @RequestMapping(path = "/emp", method = RequestMethod.GET) 111 | @ResponseBody 112 | public Map getEmp() { 113 | Map emp = new HashMap<>(); 114 | emp.put("name", "张三"); 115 | emp.put("age", 23); 116 | emp.put("salary", 8000.00); 117 | return emp; 118 | } 119 | 120 | @RequestMapping(path = "/emps", method = RequestMethod.GET) 121 | @ResponseBody 122 | public List> getEmps() { 123 | List> list = new ArrayList<>(); 124 | 125 | Map emp = new HashMap<>(); 126 | emp.put("name", "张三"); 127 | emp.put("age", 23); 128 | emp.put("salary", 8000.00); 129 | list.add(emp); 130 | 131 | emp = new HashMap<>(); 132 | emp.put("name", "李四"); 133 | emp.put("age", 24); 134 | emp.put("salary", 9000.00); 135 | list.add(emp); 136 | 137 | emp = new HashMap<>(); 138 | emp.put("name", "王五"); 139 | emp.put("age", 25); 140 | emp.put("salary", 10000.00); 141 | list.add(emp); 142 | 143 | return list; 144 | } 145 | 146 | } 147 | -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/community/service/FollowService.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.community.service; 2 | 3 | import com.nowcoder.community.entity.User; 4 | import com.nowcoder.community.utils.CommunityConstant; 5 | import com.nowcoder.community.utils.RedisKeyUtil; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.dao.DataAccessException; 8 | import org.springframework.data.redis.core.RedisOperations; 9 | import org.springframework.data.redis.core.RedisTemplate; 10 | import org.springframework.data.redis.core.SessionCallback; 11 | import org.springframework.stereotype.Service; 12 | 13 | import java.util.*; 14 | 15 | @Service 16 | public class FollowService implements CommunityConstant { 17 | 18 | @Autowired 19 | private RedisTemplate redisTemplate; 20 | 21 | @Autowired 22 | private UserService userService; 23 | 24 | //关注 25 | public void follow(int userId, int entityType, int entityId){ 26 | redisTemplate.execute(new SessionCallback() { 27 | @Override 28 | public Object execute(RedisOperations redisOperations) throws DataAccessException { 29 | String followeeKey = RedisKeyUtil.getFolloweeKey(userId, entityType); 30 | String followerKey = RedisKeyUtil.getFollowerKey(entityType, entityId); 31 | 32 | redisOperations.multi(); 33 | 34 | redisOperations.opsForZSet().add(followeeKey, entityId, System.currentTimeMillis()); 35 | redisOperations.opsForZSet().add(followerKey, userId, System.currentTimeMillis()); 36 | 37 | return redisOperations.exec(); 38 | } 39 | }); 40 | } 41 | 42 | //取消关注 43 | public void unfollow(int userId, int entityType, int entityId){ 44 | redisTemplate.execute(new SessionCallback() { 45 | @Override 46 | public Object execute(RedisOperations redisOperations) throws DataAccessException { 47 | String followeeKey = RedisKeyUtil.getFolloweeKey(userId, entityType); 48 | String followerKey = RedisKeyUtil.getFollowerKey(entityType, entityId); 49 | 50 | redisOperations.multi(); 51 | 52 | redisOperations.opsForZSet().remove(followeeKey, entityId); 53 | redisOperations.opsForZSet().remove(followerKey, userId); 54 | 55 | return redisOperations.exec(); 56 | } 57 | }); 58 | } 59 | 60 | //关注的目标实体的数量 61 | public long findFolloweeCount(int userId, int entityType){ 62 | String followeeKey = RedisKeyUtil.getFolloweeKey(userId, entityType); 63 | return redisTemplate.opsForZSet().zCard(followeeKey); 64 | } 65 | 66 | //查询实体的粉丝数量 67 | public long findFollowerCount(int userId, int entityType){ 68 | String followerKey = RedisKeyUtil.getFollowerKey(userId, entityType); 69 | return redisTemplate.opsForZSet().zCard(followerKey); 70 | } 71 | 72 | //查询当前用户是否已关注该实体 73 | public boolean hasFollowed(int userId, int entityType, int entityId){ 74 | String followeeKey = RedisKeyUtil.getFolloweeKey(userId, entityType); 75 | return redisTemplate.opsForZSet().score(followeeKey, entityId) != null; 76 | } 77 | 78 | //查询某用户关注的人 79 | public List> findFollowees(int userId, int offset, int limit){ 80 | String followeeKey = RedisKeyUtil.getFolloweeKey(userId, ENTITY_TYPE_USER); 81 | Set targetIds = redisTemplate.opsForZSet().reverseRange(followeeKey, offset, offset + limit - 1); 82 | if(targetIds == null){ 83 | return null; 84 | } 85 | 86 | List> list = new ArrayList<>(); 87 | for(Integer targetId: targetIds){ 88 | Map map = new HashMap<>(); 89 | User user = userService.findUserById(userId); 90 | map.put("user",user); 91 | Double score = redisTemplate.opsForZSet().score(followeeKey, targetId); 92 | map.put("followTime",new Date(score.longValue())); 93 | list.add(map); 94 | } 95 | 96 | return list; 97 | } 98 | 99 | //查询某用户的粉丝 100 | public List> findFollowers(int userId, int offset, int limit){ 101 | String followerKey = RedisKeyUtil.getFollowerKey(userId, ENTITY_TYPE_USER); 102 | Set targetIds = redisTemplate.opsForZSet().reverseRange(followerKey, offset, offset + limit - 1); 103 | if(targetIds == null){ 104 | return null; 105 | } 106 | 107 | List> list = new ArrayList<>(); 108 | for(Integer targetId: targetIds){ 109 | Map map = new HashMap<>(); 110 | User user = userService.findUserById(userId); 111 | map.put("user",user); 112 | Double score = redisTemplate.opsForZSet().score(followerKey, targetId); 113 | map.put("followTime",new Date(score.longValue())); 114 | list.add(map); 115 | } 116 | 117 | return list; 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /.mvn/wrapper/MavenWrapperDownloader.java: -------------------------------------------------------------------------------- 1 | /* 2 | Licensed to the Apache Software Foundation (ASF) under one 3 | or more contributor license agreements. See the NOTICE file 4 | distributed with this work for additional information 5 | regarding copyright ownership. The ASF licenses this file 6 | to you under the Apache License, Version 2.0 (the 7 | "License"); you may not use this file except in compliance 8 | with the License. You may obtain a copy of the License at 9 | 10 | https://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, 13 | software distributed under the License is distributed on an 14 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | KIND, either express or implied. See the License for the 16 | specific language governing permissions and limitations 17 | under the License. 18 | */ 19 | 20 | import java.io.File; 21 | import java.io.FileInputStream; 22 | import java.io.FileOutputStream; 23 | import java.io.IOException; 24 | import java.net.URL; 25 | import java.nio.channels.Channels; 26 | import java.nio.channels.ReadableByteChannel; 27 | import java.util.Properties; 28 | 29 | public class MavenWrapperDownloader { 30 | 31 | /** 32 | * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. 33 | */ 34 | private static final String DEFAULT_DOWNLOAD_URL = 35 | "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar"; 36 | 37 | /** 38 | * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to 39 | * use instead of the default one. 40 | */ 41 | private static final String MAVEN_WRAPPER_PROPERTIES_PATH = 42 | ".mvn/wrapper/maven-wrapper.properties"; 43 | 44 | /** 45 | * Path where the maven-wrapper.jar will be saved to. 46 | */ 47 | private static final String MAVEN_WRAPPER_JAR_PATH = 48 | ".mvn/wrapper/maven-wrapper.jar"; 49 | 50 | /** 51 | * Name of the property which should be used to override the default download url for the wrapper. 52 | */ 53 | private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; 54 | 55 | public static void main(String args[]) { 56 | System.out.println("- Downloader started"); 57 | File baseDirectory = new File(args[0]); 58 | System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); 59 | 60 | // If the maven-wrapper.properties exists, read it and check if it contains a custom 61 | // wrapperUrl parameter. 62 | File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); 63 | String url = DEFAULT_DOWNLOAD_URL; 64 | if(mavenWrapperPropertyFile.exists()) { 65 | FileInputStream mavenWrapperPropertyFileInputStream = null; 66 | try { 67 | mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); 68 | Properties mavenWrapperProperties = new Properties(); 69 | mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); 70 | url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); 71 | } catch (IOException e) { 72 | System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); 73 | } finally { 74 | try { 75 | if(mavenWrapperPropertyFileInputStream != null) { 76 | mavenWrapperPropertyFileInputStream.close(); 77 | } 78 | } catch (IOException e) { 79 | // Ignore ... 80 | } 81 | } 82 | } 83 | System.out.println("- Downloading from: : " + url); 84 | 85 | File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); 86 | if(!outputFile.getParentFile().exists()) { 87 | if(!outputFile.getParentFile().mkdirs()) { 88 | System.out.println( 89 | "- ERROR creating output direcrory '" + outputFile.getParentFile().getAbsolutePath() + "'"); 90 | } 91 | } 92 | System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); 93 | try { 94 | downloadFileFromURL(url, outputFile); 95 | System.out.println("Done"); 96 | System.exit(0); 97 | } catch (Throwable e) { 98 | System.out.println("- Error downloading"); 99 | e.printStackTrace(); 100 | System.exit(1); 101 | } 102 | } 103 | 104 | private static void downloadFileFromURL(String urlString, File destination) throws Exception { 105 | URL website = new URL(urlString); 106 | ReadableByteChannel rbc; 107 | rbc = Channels.newChannel(website.openStream()); 108 | FileOutputStream fos = new FileOutputStream(destination); 109 | fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); 110 | fos.close(); 111 | rbc.close(); 112 | } 113 | 114 | } 115 | -------------------------------------------------------------------------------- /src/main/resources/templates/error/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 牛客网-404 10 | 11 | 12 |
13 | 14 |
15 |
16 | 17 | 58 |
59 |
60 | 61 | 62 |
63 |
64 | 65 |
66 |
67 | 68 | 69 |
70 |
71 |
72 | 73 |
74 | 75 |
76 | 77 |
78 |
79 |
80 | 103 |
104 |
105 |
106 |
107 | 123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 | 131 | 132 | 133 | 134 | 135 | 136 | -------------------------------------------------------------------------------- /src/main/resources/templates/error/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 牛客网-500 10 | 11 | 12 |
13 | 14 |
15 |
16 | 17 | 58 |
59 |
60 | 61 | 62 |
63 |
64 | 65 |
66 |
67 | 68 | 69 |
70 |
71 |
72 | 73 |
74 | 75 |
76 | 77 |
78 |
79 |
80 | 103 |
104 |
105 |
106 |
107 | 123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 | 131 | 132 | 133 | 134 | 135 | 136 | -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/community/controller/MessageController.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.community.controller; 2 | 3 | import com.nowcoder.community.entity.Message; 4 | import com.nowcoder.community.entity.Page; 5 | import com.nowcoder.community.entity.User; 6 | import com.nowcoder.community.service.MessageService; 7 | import com.nowcoder.community.service.UserService; 8 | import com.nowcoder.community.utils.CommunityUtil; 9 | import com.nowcoder.community.utils.HostHolder; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.stereotype.Controller; 12 | import org.springframework.ui.Model; 13 | import org.springframework.web.bind.annotation.PathVariable; 14 | import org.springframework.web.bind.annotation.RequestMapping; 15 | import org.springframework.web.bind.annotation.RequestMethod; 16 | import org.springframework.web.bind.annotation.ResponseBody; 17 | 18 | import java.util.*; 19 | 20 | @Controller 21 | public class MessageController { 22 | 23 | @Autowired 24 | private MessageService messageService; 25 | 26 | @Autowired 27 | private HostHolder hostHolder; 28 | 29 | @Autowired 30 | private UserService userService; 31 | 32 | // 私信列表 33 | @RequestMapping(path = "/letter/list", method = RequestMethod.GET) 34 | public String getLetterList(Model model, Page page) { 35 | User user = hostHolder.getUser(); 36 | // 分页信息 37 | page.setLimit(5); 38 | page.setPath("/letter/list"); 39 | page.setRows(messageService.findConversationCount(user.getId())); 40 | 41 | // 会话列表 42 | List conversationList = messageService.findConversations( 43 | user.getId(), page.getOffset(), page.getLimit()); 44 | List> conversations = new ArrayList<>(); 45 | if (conversationList != null) { 46 | for (Message message : conversationList) { 47 | Map map = new HashMap<>(); 48 | map.put("conversation", message); 49 | map.put("letterCount", messageService.findLetterCount(message.getConversationId())); 50 | map.put("unreadCount", messageService.findLetterUnreadCount(user.getId(), message.getConversationId())); 51 | int targetId = user.getId() == message.getFromId() ? message.getToId() : message.getFromId(); 52 | map.put("target", userService.findUserById(targetId)); 53 | 54 | conversations.add(map); 55 | } 56 | } 57 | model.addAttribute("conversations", conversations); 58 | 59 | // 查询未读消息数量 60 | int letterUnreadCount = messageService.findLetterUnreadCount(user.getId(), null); 61 | model.addAttribute("letterUnreadCount", letterUnreadCount); 62 | 63 | return "/site/letter"; 64 | } 65 | 66 | @RequestMapping(path = "/letter/detail/{conversationId}", method = RequestMethod.GET) 67 | public String getLetterDetail(@PathVariable("conversationId") String conversationId, Page page, Model model) { 68 | // 分页信息 69 | page.setLimit(5); 70 | page.setPath("/letter/detail/" + conversationId); 71 | page.setRows(messageService.findLetterCount(conversationId)); 72 | 73 | // 私信列表 74 | List letterList = messageService.findLetters(conversationId, page.getOffset(), page.getLimit()); 75 | List> letters = new ArrayList<>(); 76 | if (letterList != null) { 77 | for (Message message : letterList) { 78 | Map map = new HashMap<>(); 79 | map.put("letter", message); 80 | map.put("fromUser", userService.findUserById(message.getFromId())); 81 | letters.add(map); 82 | } 83 | } 84 | model.addAttribute("letters", letters); 85 | 86 | // 私信目标 87 | model.addAttribute("target", getLetterTarget(conversationId)); 88 | 89 | // 设置已读 90 | List ids = getLetterIds(letterList); 91 | if (!ids.isEmpty()) { 92 | messageService.readMessage(ids); 93 | } 94 | 95 | return "/site/letter-detail"; 96 | } 97 | 98 | private User getLetterTarget(String conversationId) { 99 | String[] ids = conversationId.split("_"); 100 | int id0 = Integer.parseInt(ids[0]); 101 | int id1 = Integer.parseInt(ids[1]); 102 | 103 | if (hostHolder.getUser().getId() == id0) { 104 | return userService.findUserById(id1); 105 | } else { 106 | return userService.findUserById(id0); 107 | } 108 | } 109 | 110 | private List getLetterIds(List letterList) { 111 | List ids = new ArrayList<>(); 112 | 113 | if (letterList != null) { 114 | for (Message message : letterList) { 115 | if (hostHolder.getUser().getId() == message.getToId() && message.getStatus() == 0) { 116 | ids.add(message.getId()); 117 | } 118 | } 119 | } 120 | 121 | return ids; 122 | } 123 | 124 | @RequestMapping(path = "/letter/send", method = RequestMethod.POST) 125 | @ResponseBody 126 | public String sendLetter(String toName, String content) { 127 | User target = userService.findUserByName(toName); 128 | if (target == null) { 129 | return CommunityUtil.getJsonString(1, "目标用户不存在!"); 130 | } 131 | 132 | Message message = new Message(); 133 | message.setFromId(hostHolder.getUser().getId()); 134 | message.setToId(target.getId()); 135 | if (message.getFromId() < message.getToId()) { 136 | message.setConversationId(message.getFromId() + "_" + message.getToId()); 137 | } else { 138 | message.setConversationId(message.getToId() + "_" + message.getFromId()); 139 | } 140 | message.setContent(content); 141 | message.setCreateTime(new Date()); 142 | messageService.addMessage(message); 143 | 144 | return CommunityUtil.getJsonString(0); 145 | } 146 | 147 | 148 | } 149 | -------------------------------------------------------------------------------- /mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM https://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven2 Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM set title of command window 39 | title %0 40 | @REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' 41 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 42 | 43 | @REM set %HOME% to equivalent of $HOME 44 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 45 | 46 | @REM Execute a user defined script before this one 47 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 48 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 49 | if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" 50 | if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" 51 | :skipRcPre 52 | 53 | @setlocal 54 | 55 | set ERROR_CODE=0 56 | 57 | @REM To isolate internal variables from possible post scripts, we use another setlocal 58 | @setlocal 59 | 60 | @REM ==== START VALIDATION ==== 61 | if not "%JAVA_HOME%" == "" goto OkJHome 62 | 63 | echo. 64 | echo Error: JAVA_HOME not found in your environment. >&2 65 | echo Please set the JAVA_HOME variable in your environment to match the >&2 66 | echo location of your Java installation. >&2 67 | echo. 68 | goto error 69 | 70 | :OkJHome 71 | if exist "%JAVA_HOME%\bin\java.exe" goto init 72 | 73 | echo. 74 | echo Error: JAVA_HOME is set to an invalid directory. >&2 75 | echo JAVA_HOME = "%JAVA_HOME%" >&2 76 | echo Please set the JAVA_HOME variable in your environment to match the >&2 77 | echo location of your Java installation. >&2 78 | echo. 79 | goto error 80 | 81 | @REM ==== END VALIDATION ==== 82 | 83 | :init 84 | 85 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 86 | @REM Fallback to current working directory if not found. 87 | 88 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 89 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 90 | 91 | set EXEC_DIR=%CD% 92 | set WDIR=%EXEC_DIR% 93 | :findBaseDir 94 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 95 | cd .. 96 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 97 | set WDIR=%CD% 98 | goto findBaseDir 99 | 100 | :baseDirFound 101 | set MAVEN_PROJECTBASEDIR=%WDIR% 102 | cd "%EXEC_DIR%" 103 | goto endDetectBaseDir 104 | 105 | :baseDirNotFound 106 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 107 | cd "%EXEC_DIR%" 108 | 109 | :endDetectBaseDir 110 | 111 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 112 | 113 | @setlocal EnableExtensions EnableDelayedExpansion 114 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 115 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 116 | 117 | :endReadAdditionalConfig 118 | 119 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 120 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 121 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 122 | 123 | set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar" 124 | FOR /F "tokens=1,2 delims==" %%A IN (%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties) DO ( 125 | IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B 126 | ) 127 | 128 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 129 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data. 130 | if exist %WRAPPER_JAR% ( 131 | echo Found %WRAPPER_JAR% 132 | ) else ( 133 | echo Couldn't find %WRAPPER_JAR%, downloading it ... 134 | echo Downloading from: %DOWNLOAD_URL% 135 | powershell -Command "(New-Object Net.WebClient).DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')" 136 | echo Finished downloading %WRAPPER_JAR% 137 | ) 138 | @REM End of extension 139 | 140 | %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 141 | if ERRORLEVEL 1 goto error 142 | goto end 143 | 144 | :error 145 | set ERROR_CODE=1 146 | 147 | :end 148 | @endlocal & set ERROR_CODE=%ERROR_CODE% 149 | 150 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost 151 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 152 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" 153 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" 154 | :skipRcPost 155 | 156 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 157 | if "%MAVEN_BATCH_PAUSE%" == "on" pause 158 | 159 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% 160 | 161 | exit /B %ERROR_CODE% 162 | -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/community/controller/DiscussPostController.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.community.controller; 2 | 3 | import com.nowcoder.community.entity.Comment; 4 | import com.nowcoder.community.entity.DiscussPost; 5 | import com.nowcoder.community.entity.Page; 6 | import com.nowcoder.community.entity.User; 7 | import com.nowcoder.community.service.CommentService; 8 | import com.nowcoder.community.service.DiscussPostService; 9 | import com.nowcoder.community.service.LikeService; 10 | import com.nowcoder.community.service.UserService; 11 | import com.nowcoder.community.utils.CommunityConstant; 12 | import com.nowcoder.community.utils.CommunityUtil; 13 | import com.nowcoder.community.utils.HostHolder; 14 | import org.springframework.beans.factory.annotation.Autowired; 15 | import org.springframework.stereotype.Controller; 16 | import org.springframework.ui.Model; 17 | import org.springframework.web.bind.annotation.PathVariable; 18 | import org.springframework.web.bind.annotation.RequestMapping; 19 | import org.springframework.web.bind.annotation.RequestMethod; 20 | import org.springframework.web.bind.annotation.ResponseBody; 21 | 22 | import java.util.*; 23 | 24 | /** 25 | * 帖子相关 26 | */ 27 | @Controller 28 | @RequestMapping("/discuss") 29 | public class DiscussPostController implements CommunityConstant { 30 | @Autowired 31 | private DiscussPostService discussPostService; 32 | 33 | @Autowired 34 | private HostHolder hostHolder; 35 | 36 | @Autowired 37 | private UserService userService; 38 | 39 | @Autowired 40 | private CommentService commentService; 41 | 42 | @Autowired 43 | private LikeService likeService; 44 | /** 45 | * 发布帖子 46 | * @param title 47 | * @param content 48 | * @return 返回响应json 49 | */ 50 | @RequestMapping(path = "/add",method = RequestMethod.POST) 51 | @ResponseBody 52 | public String addDiscussPost(String title, String content){ 53 | User user = hostHolder.getUser(); 54 | if(user == null){ 55 | return CommunityUtil.getJsonString(403,"您还没有登录!"); 56 | } 57 | 58 | DiscussPost discussPost = new DiscussPost(); 59 | discussPost.setUserId(user.getId()); 60 | discussPost.setTitle(title); 61 | discussPost.setContent(content); 62 | discussPost.setCreateTime(new Date()); 63 | discussPostService.addDiscussPost(discussPost); 64 | 65 | return CommunityUtil.getJsonString(0,"发布成功!"); 66 | } 67 | 68 | /** 69 | * 帖子详情 70 | * @param discussPostId 71 | * @param model 72 | * @return 73 | */ 74 | @RequestMapping(path = "/detail/{discussPostId}",method = RequestMethod.GET) 75 | public String getDiscussPost(@PathVariable("discussPostId") int discussPostId, Model model, Page page){ 76 | //帖子,每次查询效率就低了,后期存入redis 77 | DiscussPost post = discussPostService.findDiscussPostById(discussPostId); 78 | model.addAttribute("post",post); 79 | 80 | //作者 81 | User user = userService.findUserById(post.getUserId()); 82 | model.addAttribute("user",user); 83 | 84 | //点赞 85 | long entityLikeCount = likeService.findEntityLikeCount(ENTITY_TYPE_POST, post.getId()); 86 | model.addAttribute("likeCount",entityLikeCount); 87 | 88 | //点赞状态 89 | int entityLikeStatus = hostHolder.getUser()==null ? 0 : 90 | likeService.findEntityLikeStatus(user.getId(), ENTITY_TYPE_POST, post.getId()); 91 | model.addAttribute("likeStatus",entityLikeStatus); 92 | 93 | 94 | // 评论分页信息 95 | page.setLimit(5); 96 | page.setPath("/discuss/detail/"+discussPostId); 97 | page.setRows(post.getCommentCount()); 98 | 99 | // 评论:给帖子的评论 100 | // 回复:给评论的评论 101 | //根据帖子的id分页找出所有的评论 102 | List comments = 103 | commentService.selectCommentsByEntity(ENTITY_TYPE_POST, post.getId(), page.getOffset(), page.getLimit()); 104 | 105 | List> commentVoList = new ArrayList<>(); 106 | if(comments != null){ 107 | //将每条评论的评论人、评论信息插入map 108 | for(Comment comment: comments){ 109 | Map map = new HashMap<>(); 110 | map.put("comment",comment); 111 | map.put("user",userService.findUserById(comment.getUserId())); 112 | //点赞 113 | entityLikeCount = likeService.findEntityLikeCount(ENTITY_TYPE_COMMENT, comment.getId()); 114 | map.put("likeCount",entityLikeCount); 115 | //点赞状态 116 | entityLikeStatus = hostHolder.getUser()==null ? 0 : 117 | likeService.findEntityLikeStatus(hostHolder.getUser().getId(), ENTITY_TYPE_COMMENT, comment.getId()); 118 | map.put("likeStatus",entityLikeStatus); 119 | 120 | //二级评论 121 | List replyList = 122 | commentService.selectCommentsByEntity(ENTITY_TYPE_COMMENT, comment.getId(), 0, Integer.MAX_VALUE); 123 | List> replyVoList = new ArrayList<>(); 124 | if(replyList != null){ 125 | for(Comment reply: replyList){ 126 | Map rmap = new HashMap<>(); 127 | rmap.put("reply",reply); 128 | rmap.put("user",userService.findUserById(reply.getUserId())); 129 | //回复目标 130 | User target = reply.getTargetId() == 0 ? null : userService.findUserById(reply.getTargetId()); 131 | rmap.put("target",target); 132 | //点赞 133 | entityLikeCount = likeService.findEntityLikeCount(ENTITY_TYPE_COMMENT, reply.getId()); 134 | rmap.put("likeCount",entityLikeCount); 135 | //点赞状态 136 | entityLikeStatus = hostHolder.getUser()==null ? 0 : 137 | likeService.findEntityLikeStatus(hostHolder.getUser().getId(), ENTITY_TYPE_COMMENT, reply.getId()); 138 | rmap.put("likeStatus",entityLikeStatus); 139 | replyVoList.add(rmap); 140 | } 141 | } 142 | map.put("replys",replyVoList); 143 | //回复数量 144 | int commentCount = commentService.selectCountByEntity(ENTITY_TYPE_COMMENT, comment.getId()); 145 | map.put("replyCount",commentCount); 146 | commentVoList.add(map); 147 | } 148 | } 149 | 150 | model.addAttribute("comments",commentVoList); 151 | return "/site/discuss-detail"; 152 | 153 | } 154 | 155 | 156 | } 157 | -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/community/controller/UserController.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.community.controller; 2 | 3 | import com.nowcoder.community.annotation.LoginRequired; 4 | import com.nowcoder.community.entity.User; 5 | import com.nowcoder.community.service.FollowService; 6 | import com.nowcoder.community.service.LikeService; 7 | import com.nowcoder.community.service.UserService; 8 | import com.nowcoder.community.utils.CommunityConstant; 9 | import com.nowcoder.community.utils.CommunityUtil; 10 | import com.nowcoder.community.utils.HostHolder; 11 | import org.apache.commons.lang3.StringUtils; 12 | import org.slf4j.Logger; 13 | import org.slf4j.LoggerFactory; 14 | import org.springframework.beans.factory.annotation.Autowired; 15 | import org.springframework.beans.factory.annotation.Value; 16 | import org.springframework.stereotype.Controller; 17 | import org.springframework.ui.Model; 18 | import org.springframework.web.bind.annotation.PathVariable; 19 | import org.springframework.web.bind.annotation.RequestMapping; 20 | import org.springframework.web.bind.annotation.RequestMethod; 21 | import org.springframework.web.multipart.MultipartFile; 22 | 23 | import javax.servlet.ServletOutputStream; 24 | import javax.servlet.http.HttpServletResponse; 25 | import java.io.File; 26 | import java.io.FileInputStream; 27 | import java.io.IOException; 28 | 29 | 30 | @Controller 31 | @RequestMapping("/user") 32 | public class UserController implements CommunityConstant{ 33 | private static final Logger LOGGER = LoggerFactory.getLogger(UserController.class); 34 | 35 | @Value("${community.path.upload}") 36 | private String uploadPath; 37 | 38 | @Value("${community.path.domain}") 39 | private String domain; 40 | 41 | @Value("${server.servlet.context-path}") 42 | private String contextPath; 43 | 44 | @Autowired 45 | private UserService userService; 46 | 47 | @Autowired 48 | private HostHolder hostHolder; 49 | 50 | @Autowired 51 | private LikeService likeService; 52 | 53 | @Autowired 54 | private FollowService followService; 55 | /** 56 | * 跳转设置界面 57 | * @return 58 | */ 59 | @LoginRequired 60 | @RequestMapping(path = "/setting",method = RequestMethod.GET) 61 | public String getSettingPage(){ 62 | return "/site/setting"; 63 | } 64 | 65 | /** 66 | * 上传头像 67 | * @param headImg 68 | * @param model 69 | * @return 70 | */ 71 | @LoginRequired 72 | @RequestMapping(path = "/upload",method = RequestMethod.POST) 73 | public String uploadHeader(MultipartFile headImg, Model model){ 74 | if(headImg == null){ 75 | model.addAttribute("error","您还没有选择图片!"); 76 | return "/site/setting"; 77 | } 78 | 79 | //原始文件名 80 | String originalFilename = headImg.getOriginalFilename(); 81 | //文件后缀 82 | String suffix = originalFilename.substring(originalFilename.lastIndexOf(".")); 83 | if(StringUtils.isBlank(suffix)){ 84 | model.addAttribute("error","文件格式不正确!"); 85 | return "/site/setting"; 86 | } 87 | 88 | //生成随机文件名,防止文件名冲突 89 | String filename = CommunityUtil.generateUUID() + suffix; 90 | //确定文件存放路径 91 | File dest = new File(uploadPath + "/" + filename); 92 | try { 93 | //存储文件 94 | headImg.transferTo(dest); 95 | } catch (IOException e) { 96 | LOGGER.error("上传文件失败:"+e.getMessage()); 97 | throw new RuntimeException("上传文件失败,服务器发生异常!",e); 98 | } 99 | 100 | //更新当前用户的头像的路径(web访问路径) 101 | //http:localhost:8080/community/user/header/xxx.png 102 | User user = hostHolder.getUser(); 103 | String headerUrl = domain+contextPath+"/user/header/"+filename; 104 | userService.updateHeader(user.getId(),headerUrl); 105 | 106 | return "redirect:/index"; 107 | } 108 | 109 | @RequestMapping(path = "/header/{filename}",method = RequestMethod.GET) 110 | public void getHeadImg(@PathVariable("filename") String filename, HttpServletResponse response){ 111 | // 获取服务器存放路径 112 | filename = uploadPath + "/" + filename; 113 | //文件后缀 114 | String suffix = filename.substring(filename.lastIndexOf(".")); 115 | //响应图片 116 | response.setContentType("image/"+suffix); 117 | try (FileInputStream fileInputStream = new FileInputStream(filename); 118 | ServletOutputStream os = response.getOutputStream(); 119 | ){ 120 | byte[] buffer = new byte[1024]; //建一个缓冲区 121 | int b = 0; 122 | while((b=fileInputStream.read(buffer))!=-1){ 123 | os.write(buffer,0,b); 124 | } 125 | } catch (IOException e) { 126 | LOGGER.error("读取头像失败:"+e.getMessage()); 127 | } 128 | 129 | } 130 | 131 | @RequestMapping(path = "/modifyPwd",method = RequestMethod.POST) 132 | public String modifyPwd(String oldPassword,String newPassword,String checkPassword, Model model){ 133 | 134 | User user = hostHolder.getUser(); 135 | String old = CommunityUtil.md5(oldPassword + user.getSalt()); 136 | if(!old.equals(user.getPassword())){ 137 | model.addAttribute("pwdError1","原密码错误!"); 138 | return "/site/setting"; 139 | } 140 | if(!newPassword.equals(checkPassword)){ 141 | model.addAttribute("pwdError2","两次输入的密码不一致!"); 142 | return "/site/setting"; 143 | } 144 | 145 | String New = CommunityUtil.md5(newPassword+user.getSalt()); 146 | userService.updatePassword(user.getId(), New); 147 | return "redirect:/index"; 148 | } 149 | 150 | //个人主页 151 | @RequestMapping(path = "/profile/{userId}",method = RequestMethod.GET) 152 | public String getProfilePage(@PathVariable("userId") int userId, Model model){ 153 | 154 | User user = userService.findUserById(userId); 155 | if(user == null){ 156 | throw new IllegalArgumentException("该用户不存在!"); 157 | } 158 | 159 | //用户 160 | model.addAttribute("user",user); 161 | int userLikeCount = likeService.findUserLikeCount(userId); 162 | //点赞数量 163 | model.addAttribute("likeCount",userLikeCount); 164 | 165 | //关注数量 166 | long followeeCount = followService.findFolloweeCount(userId, ENTITY_TYPE_USER); 167 | model.addAttribute("followeeCount", followeeCount); 168 | //粉丝数量 169 | long followerCount = followService.findFollowerCount(userId, ENTITY_TYPE_USER); 170 | model.addAttribute("followerCount", followerCount); 171 | //是否已关注 172 | boolean hasFollowed = false; 173 | if(hostHolder.getUser() != null){ 174 | hasFollowed = followService.hasFollowed(hostHolder.getUser().getId(), ENTITY_TYPE_USER,userId); 175 | } 176 | model.addAttribute("hasFollowed",hasFollowed); 177 | 178 | return "/site/profile"; 179 | } 180 | 181 | 182 | } 183 | -------------------------------------------------------------------------------- /src/main/resources/templates/site/admin/data.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 牛客网-数据统计 10 | 11 | 12 |
13 | 14 |
15 |
16 | 17 | 58 |
59 |
60 | 61 | 62 |
63 | 64 |
65 |
网站 UV
66 |
67 | 68 | 69 | 70 |
71 |
    72 |
  • 73 | 统计结果 74 | 0 75 |
  • 76 |
77 |
78 | 79 |
80 |
活跃用户
81 |
82 | 83 | 84 | 85 |
86 |
    87 |
  • 88 | 统计结果 89 | 0 90 |
  • 91 |
92 |
93 |
94 | 95 | 96 |
97 |
98 |
99 | 100 |
101 | 102 |
103 | 104 |
105 |
106 |
107 | 130 |
131 |
132 |
133 |
134 | 150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 主要技术栈 2 | 后端:Springboot + MyBatis + Redis + ElasticSearch 目前正在学习Kafka,增加系统消息通知功能 3 | 前端:Bootstrap 4 | 5 | ## 1、论坛首页 6 | 7 | > 流程 8 | 9 | + 一次请求的执行过程 10 | 11 | > 分步实现 12 | 13 | - 开发社区首页,显示10个帖子 14 | - 开发分页组件,分页显示所有帖子 15 | 16 | 17 | 18 | ## 2、发送邮件 19 | 20 | > 邮箱设置 21 | 22 | + 启用客户端SMTP服务(这里选择QQ邮箱) 23 | 24 | > Spring Email 配置 25 | 26 | + 导入依赖包 27 | + 邮箱参数配置 28 | + 使用JavaMailSender 发送邮件 29 | 30 | > 模板引擎 31 | 32 | + 使用Thymeleaf发送HTML邮件 33 | 34 | 35 | 36 | ## 3、注册功能 37 | 38 | > 访问注册页面 39 | 40 | + 点击顶部导航栏注册按钮,打开注册页面 41 | 42 | > 提交注册数据 43 | 44 | + 通过表单提交注册数据 45 | + 服务端验证账号是否已存在、邮箱是否已注册 46 | + 服务端发送激活邮件 47 | 48 | > 激活注册账号 49 | 50 | + 点击邮件中的链接,访问服务端的激活服务 51 | 52 | 53 | 54 | ## 4、会话管理 55 | 56 | > http的基本性质 57 | 58 | + 简单的 59 | + 可扩展 60 | + 无状态,有会话 61 | 62 | > Cookie 63 | 64 | + 是服务器发送到浏览器,并保存在浏览器端的小块数据。 65 | + 浏览器下次访问该服务器时,会自动携带该块数据,将其发送给服务器。 66 | 67 | > session 68 | 69 | + 是JAVAEE的标准,用于在服务端记录客户端信息。 70 | + 数据存放在服务端更加安全,但是也会增加服务端的内存压力。 71 | 72 | 73 | 74 | 众所周知,session是存在于服务端的。这里就有几个问题。 75 | 76 | 如果多个用户访问同一台服务器,那这台服务器可能会无法响应大访问量而宕机,因此可以选择用nginx做一个负载均衡。 77 | 78 | > 当使用负载均衡后,session的共享就出现了问题。 79 | 80 | 用户一访问服务器,nginx找到当前较为空闲的服务器一去处理请求,服务器一返回一个cookie(sessionId) 来唯一标识客户端主机。此时该用户携带cookie再次访问服务器,nginx给他分配了另一台服务器二去处理请求,但是服务器二内没有相应的sessionId,因此客户端与服务器端无法建立会话。 81 | 82 | > 解决方案 83 | 84 | 1、粘性session: 哪台服务器有sessionId,就给这个用户分配到这台服务器上。这是最简单粗暴的办法,但是显然是不合理的。如果这样做了,那么负载均衡就没有意义了,因为并不均衡。这就是粘性session。 85 | 86 | 2、同步session:做一个服务器间同步session。一台服务器处理完请求后,就同步其它服务器。虽然能解决session共享问题,但是增大了服务器间的耦合度,会对服务器性能造成影响。 87 | 88 | 3、共享session:创建一台专门存储session的服务器,别的服务器向这台服务器申请session。 但是如果这台服务器宕机,那么其他服务器全部无法工作了。 89 | 90 | **4、目前主流办法:**能存cookie就存cookie。敏感数据就存在数据库中。可以对数据库做一个集群、主从复制,这样既能保证性能,也能保证会话安全性。但是,传统的关系型数据库,数据是存储在磁盘中的,每次从数据库获取数据都要进行磁盘IO,性能是较低的。 91 | 92 | 所以现在大家都选择用NoSql数据库,如Redis,来存储数据。Redis数据存储在内存中,读取数据更快。 93 | 94 | ![image-20200430194249301](E:\myyyyyyyyyyyyyynote\仿牛客\session存储.png) 95 | 96 | 97 | 98 | ## 5、生成验证码 99 | 100 | > Kaptcha 101 | 102 | + 导入依赖包 103 | + 编写Kaptcha配置类 104 | + 生成随机字符、生成图片 105 | 106 | 107 | 108 | ## 6、登录、退出功能 109 | 110 | > 访问登录页面 111 | 112 | + 点击顶部导航栏的登录按钮,打开登录页面 113 | 114 | > 登录 115 | 116 | + 验证账号、密码、验证码 117 | + 成功时,生成登录凭证,发给客户端 118 | + 失败时,跳转回登录页,并返回错误信息 119 | 120 | > 退出 121 | 122 | + 将登录凭证修改为失效状态 123 | + 跳转至网站首页 124 | 125 | 126 | 127 | ![登录流程](E:\myyyyyyyyyyyyyynote\仿牛客\登录流程.PNG) 128 | 129 | ## 7、显示登录信息 130 | 131 | > 拦截器 132 | 133 | + 定义拦截器,实现HandlerInterceptor 134 | + 配置拦截器,为它指定拦截、排除的路径 135 | 136 | > 拦截应用 137 | 138 | + 在请求开始时查询登录用户 139 | + 在本次请求中持有用户数据 140 | + 在模板视图上显示用户数据 141 | + 在请求结束时清理用户数据 142 | 143 | 144 | 145 | ## 8、账号设置 146 | 147 | > 上传文件(头像) 148 | 149 | + 请求:必须是POST 150 | + 表单:enctype=“multipart/form-data” 151 | + Spring MVC : 通过MuitipartFile处理上传文件 152 | 153 | > 修改密码 154 | 155 | > 开发步骤 156 | 157 | + 访问账号设置页面 158 | + 上传头像 159 | + 获取头像 160 | 161 | 162 | 163 | ## 9、检查登录状态 164 | 165 | > 使用拦截器 166 | 167 | + 在方法前标注自定义注解 168 | + 拦截所有请求,只处理带有该注解的方法 169 | 170 | > 使用自定义注解 171 | 172 | + 常用的元注解: 173 | 174 | @Target : 声明自定义注解的作用位置,作用类型 175 | 176 | @Retention:注解有效时期,运行时有效还是编译器有效 177 | 178 | @Document:自定义注解生成文档时是否带上 179 | 180 | @Inherited:子类继承父类,父类有自定义注解,子类是否要带上父类的注解 181 | 182 | + 如何读取注解:反射 183 | 184 | Method.getDeclaredAnnotations() 185 | 186 | Method.getAnnotation(Class annotationClass) 187 | 188 | 189 | 190 | 使用拦截器拦截注解。 191 | 192 | 193 | 194 | ## 10、过滤敏感词 195 | 196 | > 前缀树 197 | 198 | + 名称:Trie、字典树、查找树 199 | + 特点:查找效率高,消耗内存大 200 | + 应用:字符串检索、词频统计、字符串排序 201 | 202 | > 敏感词过滤器 203 | 204 | + 定义前缀树 205 | + 根据敏感词,初始化前缀树 206 | + 编写过滤敏感词的方法 207 | 208 | 209 | 210 | ## 11、发布帖子 211 | 212 | > AJAX 213 | 214 | + Asynchronous JavaScript and XML 215 | + 异步的JavaScript与XML,不是一门新技术,只是一个新的术语 216 | + 使用AJAX, 网页能够将增量更新呈现在页面上,而不需要刷新整个页面 217 | + 虽然X代码XML,但目前JSON的使用比XML更加普遍 218 | 219 | > 发布帖子 220 | 221 | + 采用AJAX请求,实现发布帖子。 222 | 223 | 224 | 225 | ## 12、帖子详情 226 | 227 | > 步骤 228 | 229 | + 数据访问层 DiscussPostMapper 230 | + 业务层 DiscussPostService 231 | + 控制层 DiscussPostController 232 | 233 | + index.html增加详情页链接 234 | + discuss-detail.html编辑 235 | 236 | 237 | 238 | ## 13、事务管理 239 | 240 | > 声明式事务 241 | 242 | + 通过XML配置,声明某方法的事务特征 243 | + 通过注解,声明某方法的事务特征 244 | 245 | > 编程式事务 246 | 247 | + 通过 TransactionTemplate管理事务,并通过它执行数据库操作。 248 | 249 | 250 | 251 | ## 14、显示评论 252 | 253 | > 数据层 254 | 255 | + 根据实体插叙一页评论数据 256 | + 根据实体查询评论的数量 257 | 258 | > 业务层 259 | 260 | + 处理查询评论的业务 261 | + 处理查询评论数量的业务 262 | 263 | > 表现层 264 | 265 | + 显示帖子详情数据时,同时显示该帖子所有评论数据。 266 | 267 | 268 | 269 | ## 15、私信功能 270 | 271 | > 私信列表 272 | 273 | + 查询当前用户的会话列表,每个会话只显示一条最新的私信 274 | + 支持分页显示 275 | 276 | > 私信详情 277 | 278 | + 查询某个会话所包含的私信 279 | + 支持分页显示 280 | 281 | > 发送私信 282 | 283 | + 采用异步的方式发送私信 284 | + 发送成功后刷新私信列表 285 | 286 | > 设置已读 287 | 288 | + 访问私信详情时,将显示的私信设置为已读状态 289 | 290 | 291 | 292 | ## 16、统一异常处理 293 | 294 | MVC三层架构。 295 | 296 | 前端发送数据,表现层接收数据,业务层执行业务逻辑,调用数据传输层方法。如果数据传输层出现异常,就会抛给业务层,业务层再抛给控表现层。 297 | 298 | > @ControllerAdvice 299 | 300 | + 用于修饰类,表示该类是Controller的全局配置类 301 | 302 | + 在此类中,可以对Controller进行如下三种全局配置: 303 | 304 | + 异常处理方案、绑定数据方案、绑定参数方案 305 | 306 | 307 | 308 | > @ExceptionHandler 309 | 310 | + 用于修饰方法,该方法会在Controller出现异常后被调用,用于处理捕获到的异常 311 | 312 | 313 | 314 | > @ModelAttribute 315 | 316 | + 用于修饰方法,该方法会在Controller方法执行前被调用,用于为Model对象绑定参数 317 | 318 | 319 | 320 | > @DataBinder 321 | 322 | + 用于修饰方法,该方法会在Controller方法执行前被调用,用于绑定参数的转换器。 323 | 324 | 325 | 326 | ## 17、统一记录日志 327 | 328 | > 需求 329 | 330 | + 帖子模块 331 | + 评论模块 332 | + 消息模块 333 | 334 | > AOP 面向切面编程 335 | 336 | ![AOP](E:\myyyyyyyyyyyyyynote\仿牛客\AOP.PNG) 337 | 338 | 339 | 340 | > AOP实现 341 | 342 | + AspectJ 343 | + 是语言级的实现,扩展了Java语法,定义了AOP语法 344 | + 在编译器织入代码,它有一个专门的编译器,用来生成遵守Java字节码规范的class文件 345 | + Spring AOP 346 | - 纯Java实现,它不需要专门的编译过程,也不需要特殊的类加载器 347 | - 在运行时通过代理的方式织入代码,只支持方法类型的连接点 348 | - Spring支持对AspectJ的集成 349 | 350 | 351 | 352 | > Spring AOP实现 353 | 354 | + JDK动态代理 355 | + java提供的动态代理技术,可以在运行时创建接口的代理实例 356 | + Spring AOP默认采用此种方式,在**接口**的代理实例中织入代码 357 | + CGLib动态代理 358 | + 采用底层的字节码技术,在运行时创建子类代理实例。 359 | + 当目标对象不存在接口时,Spring AOP会采用此种方式,在子类实例中织入代码 360 | 361 | 362 | 363 | ## 18、点赞功能 364 | 365 | > 点赞 366 | 367 | + 支持对帖子、评论点赞 368 | + 支持第1次点赞,第二次取消点赞 369 | 370 | > 首页点赞数量 371 | 372 | + 统计帖子的点赞数量 373 | 374 | > 详情页点赞数量 375 | 376 | + 统计点赞数量 377 | + 显示点赞状态 378 | 379 | > redis 380 | 381 | 避免多个用户同时点赞使得数据不一致,使用redis实现 382 | 383 | 384 | 385 | ## 19、收到的赞 386 | 387 | > 重构点赞功能 388 | 389 | + 以用户为key,记录点赞数量 390 | + increment(key), decrement(key) 391 | 392 | > 开发个人主页 393 | 394 | + 以用户为key, 查询点赞数量 395 | 396 | 397 | 398 | ## 20、关注和取消关注 399 | 400 | > 需求 401 | 402 | + 开发关注、取消关注功能 403 | + 统计用户的关注数、粉丝数 404 | 405 | > 关键 406 | 407 | + 若A关注了B, 则A是B的 follower(粉丝), B是A的 followee(目标) 408 | + 关注的目标可以是用户、帖子、题目等,在实现时将这些目标抽象为实体 409 | 410 | 411 | 412 | 413 | 414 | ## 21、关注列表、粉丝列表 415 | 416 | > 业务层 417 | 418 | + 查询某个用户关注的人,支持分页 419 | + 查询某个用户的粉丝,支持分页 420 | 421 | > 表现层 422 | 423 | + 处理“查询关注的人”、“查询粉丝”请求 424 | + 编写“查询关注的人”、“查询粉丝”模板 425 | 426 | 427 | 428 | ## 22、优化登录模块 429 | 430 | > 使用Redis存储验证码 431 | 432 | + 验证码需要频繁的访问与刷新,对性能要求较高 433 | + 验证码不需永久保存,通常在很短的时间就会失效 434 | + 分布式部署时,存在Session共享的问题 435 | 436 | > 使用Redis存储登录凭证 437 | 438 | + 处理每次请求时,都要查询用户的登录凭证 439 | 440 | > 使用Redis缓存用户信息 441 | 442 | + 处理每次请求时,都要根据凭证查询用户信息,访问的频率非常高 443 | -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/community/controller/LoginController.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.community.controller; 2 | 3 | import com.google.code.kaptcha.Producer; 4 | import com.nowcoder.community.entity.User; 5 | import com.nowcoder.community.service.UserService; 6 | import com.nowcoder.community.utils.CommunityConstant; 7 | import com.nowcoder.community.utils.CommunityUtil; 8 | import com.nowcoder.community.utils.RedisKeyUtil; 9 | import org.apache.commons.lang3.StringUtils; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | import org.springframework.beans.factory.annotation.Value; 14 | import org.springframework.data.redis.core.RedisTemplate; 15 | import org.springframework.stereotype.Controller; 16 | import org.springframework.ui.Model; 17 | import org.springframework.web.bind.annotation.CookieValue; 18 | import org.springframework.web.bind.annotation.PathVariable; 19 | import org.springframework.web.bind.annotation.RequestMapping; 20 | import org.springframework.web.bind.annotation.RequestMethod; 21 | 22 | import javax.imageio.ImageIO; 23 | import javax.servlet.http.Cookie; 24 | import javax.servlet.http.HttpServletResponse; 25 | import java.awt.image.BufferedImage; 26 | import java.io.IOException; 27 | import java.io.OutputStream; 28 | import java.util.Map; 29 | import java.util.concurrent.TimeUnit; 30 | 31 | 32 | @Controller 33 | public class LoginController implements CommunityConstant { 34 | 35 | private static final Logger LOGGER = LoggerFactory.getLogger(LoginController.class); 36 | @Autowired 37 | private Producer kaptchaProducer; 38 | @Autowired 39 | private UserService userService; 40 | @Value("${server.servlet.context-path}") 41 | private String contextPath; 42 | 43 | @Autowired 44 | private RedisTemplate redisTemplate; 45 | 46 | @RequestMapping(path="/register",method = RequestMethod.GET) 47 | public String getRegisterPage(){ 48 | return "/site/register"; 49 | } 50 | 51 | @RequestMapping(path="/login",method = RequestMethod.GET) 52 | public String getLoginPage(){ 53 | return "/site/login"; 54 | } 55 | @RequestMapping(path="/register",method=RequestMethod.POST) 56 | public String register(Model model, User user){ 57 | 58 | Map map = userService.register(user); //map是错误信息 59 | if(map == null || map.isEmpty()){ //注册成功 60 | model.addAttribute("msg","注册成功,我们已经向您的邮箱发送一份激活邮件,请尽快激活!"); 61 | model.addAttribute("target","/index"); 62 | return "/site/operate-result"; 63 | }else{ 64 | model.addAttribute("usernameMsg",map.get("usernameMsg")); 65 | model.addAttribute("passwordMsg",map.get("passwordMsg")); 66 | model.addAttribute("emailMsg",map.get("emailMsg")); 67 | 68 | return "/site/register"; 69 | } 70 | } 71 | 72 | /** 73 | * 激活 74 | * @param model 75 | * @param userId 76 | * @param code 77 | * @return 78 | */ 79 | @RequestMapping(path="/activation/{userId}/{code}",method = RequestMethod.GET) 80 | public String activation(Model model, 81 | @PathVariable("userId") int userId, 82 | @PathVariable("code") String code){ 83 | 84 | int result = userService.activation(userId, code); 85 | if(result == ACTIVATION_SUCCESS){ 86 | model.addAttribute("msg","激活成功,账号可以正常使用了!"); 87 | model.addAttribute("target","/login"); 88 | }else if(result == ACTIVATION_REPEAT){ 89 | model.addAttribute("msg","无效操作,该账号已经激活过了!"); 90 | model.addAttribute("target","/index"); 91 | }else{ 92 | model.addAttribute("msg","激活失败,激活码不正确!"); 93 | model.addAttribute("target","/index"); 94 | } 95 | return "/site/operate-result"; 96 | } 97 | 98 | /** 99 | * 获取验证码 100 | * @param response 101 | * @param session 102 | */ 103 | @RequestMapping(path = "/kaptcha",method = RequestMethod.GET) 104 | public void getKaptcha(HttpServletResponse response/*, HttpSession session*/){ 105 | // 生成验证码 106 | String text = kaptchaProducer.createText(); 107 | BufferedImage image = kaptchaProducer.createImage(text); 108 | 109 | // //将验证码存入session 110 | // session.setAttribute("kaptcha",text); 111 | 112 | //验证码的归属 113 | String kaptchaOwner = CommunityUtil.generateUUID(); 114 | Cookie cookie = new Cookie("kaptchaOwner",kaptchaOwner); 115 | cookie.setMaxAge(60); 116 | cookie.setPath(contextPath); 117 | response.addCookie(cookie); //存入cookie 118 | 119 | //将验证码存入Redis 120 | String kaptchaKey = RedisKeyUtil.getKaptchaKey(kaptchaOwner); 121 | redisTemplate.opsForValue().set(kaptchaKey,text,60, TimeUnit.SECONDS); 122 | //将图片输出给浏览器 123 | response.setContentType("image/png"); 124 | try { 125 | //获取图片输出流 126 | OutputStream outputStream = response.getOutputStream(); 127 | ImageIO.write(image, "png",outputStream); 128 | } catch (IOException e) { 129 | LOGGER.error("响应验证码失败:"+e.getMessage()); 130 | } 131 | 132 | } 133 | 134 | 135 | /** 136 | * 登录 137 | * @param username 138 | * @param password 139 | * @param code //验证码 140 | * @param rememberMe //记住我功能 141 | * @param model 142 | * @param session //取验证码用于判断 143 | * @param response //将登录信息存入cookie 144 | * @return 145 | */ 146 | @RequestMapping(path = "/login",method = RequestMethod.POST) 147 | public String login(String username, String password, String code, boolean rememberMe, 148 | Model model/*,HttpSession session*/, HttpServletResponse response, 149 | @CookieValue("kaptchaOwner") String kaptchaOwner){ 150 | String kaptcha = null; 151 | 152 | if(StringUtils.isNotBlank(kaptchaOwner)){ 153 | String kaptchaKey = RedisKeyUtil.getKaptchaKey(kaptchaOwner); 154 | kaptcha = (String) redisTemplate.opsForValue().get(kaptchaKey); 155 | 156 | } 157 | if(StringUtils.isBlank(kaptcha) || StringUtils.isBlank(code) || !kaptcha.equalsIgnoreCase(code)){ 158 | model.addAttribute("codeMsg","验证码不正确"); 159 | return "/site/login"; 160 | } 161 | 162 | //检查账号密码 163 | int expiredTime = rememberMe ? REMEMBER_EXPIRED_SECONDS : DEFAULT_EXPIRED_SECONDS; 164 | Map map = userService.login(username, password, expiredTime); 165 | if(map.containsKey("ticket")){ //登录成功,服务端返回cookie,存到客户端中。 166 | Cookie cookie = new Cookie("ticket", map.get("ticket").toString()); 167 | cookie.setPath(contextPath); 168 | cookie.setMaxAge(expiredTime); 169 | response.addCookie(cookie); 170 | return "redirect:/index"; 171 | }else{ 172 | model.addAttribute("usernameMsg",map.get("usernameMsg")); 173 | model.addAttribute("passwordMsg",map.get("passwordMsg")); 174 | return "/site/login"; 175 | } 176 | 177 | } 178 | 179 | @RequestMapping(path = "/logout",method = RequestMethod.GET) 180 | public String logout(@CookieValue("ticket") String ticket){ 181 | userService.logout(ticket); 182 | return "redirect:/login"; 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /src/main/resources/templates/site/forget.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 牛客网-忘记密码 11 | 12 | 13 |
14 | 15 |
16 |
17 | 18 | 59 |
60 |
61 | 62 | 63 |
64 |
65 |
66 |
67 | 68 |
69 | 70 |
71 | 该邮箱已被注册! 72 |
73 |
74 |
75 |
76 | 77 |
78 | 79 |
80 | 验证码不正确! 81 |
82 |
83 |
84 | 获取验证码 85 |
86 |
87 |
88 | 89 |
90 | 91 |
92 | 密码长度不能小于8位! 93 |
94 |
95 |
96 |
97 |
98 |
99 | 100 |
101 |
102 |
103 |
104 |
105 | 106 | 107 |
108 |
109 |
110 | 111 |
112 | 113 |
114 | 115 |
116 |
117 |
118 | 141 |
142 |
143 |
144 |
145 | 161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | -------------------------------------------------------------------------------- /src/main/resources/templates/site/profile.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 牛客网-个人主页 10 | 11 | 12 |
13 | 14 |
15 |
16 | 17 | 58 |
59 |
60 | 61 | 62 |
63 |
64 | 65 |
66 | 77 |
78 | 79 |
80 | 用户头像 81 |
82 |
83 | nowcoder 84 | 85 | 87 |
88 |
89 | 注册于 2015-06-12 15:20:12 90 |
91 |
92 | 关注了 5 93 | 关注者 123 94 | 获得了 87 个赞 95 |
96 |
97 |
98 |
99 |
100 | 101 | 102 |
103 |
104 |
105 | 106 |
107 | 108 |
109 | 110 |
111 |
112 |
113 | 136 |
137 |
138 |
139 |
140 | 156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | -------------------------------------------------------------------------------- /src/main/resources/templates/site/register.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 牛客网-注册 11 | 12 | 13 |
14 | 15 |
16 |
17 | 18 | 59 |
60 |
61 | 62 | 63 |
64 |
65 |

注  册

66 |
67 |
68 | 69 |
70 | 74 |
75 | 该账号已存在! 76 |
77 |
78 |
79 |
80 | 81 |
82 | 86 |
87 | 密码长度不能小于8位! 88 |
89 |
90 |
91 |
92 | 93 |
94 | 97 |
98 | 两次输入的密码不一致! 99 |
100 |
101 |
102 |
103 | 104 |
105 | 109 |
110 | 该邮箱已注册! 111 |
112 |
113 |
114 |
115 |
116 |
117 | 118 |
119 |
120 |
121 |
122 |
123 | 124 | 125 |
126 |
127 |
128 | 129 |
130 | 131 |
132 | 133 |
134 |
135 |
136 | 159 |
160 |
161 |
162 |
163 | 179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | -------------------------------------------------------------------------------- /src/main/resources/templates/site/followee.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 牛客网-关注 10 | 11 | 12 |
13 | 14 |
15 |
16 | 17 | 58 |
59 |
60 | 61 | 62 |
63 |
64 | 80 | 81 | 82 |
    83 |
  • 84 | 85 | 用户头像 86 | 87 |
    88 |
    89 | 落基山脉下的闲人 90 | 91 | 关注于 2019-04-28 14:13:25 92 | 93 |
    94 |
    95 | 96 | 98 |
    99 |
    100 |
  • 101 |
102 | 103 | 116 |
117 |
118 | 119 | 120 |
121 |
122 |
123 | 124 |
125 | 126 |
127 | 128 |
129 |
130 |
131 | 154 |
155 |
156 |
157 |
158 | 174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | -------------------------------------------------------------------------------- /src/main/resources/templates/site/follower.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 牛客网-关注 10 | 11 | 12 |
13 | 14 |
15 |
16 | 17 | 58 |
59 |
60 | 61 | 62 |
63 |
64 | 80 | 81 | 82 |
    83 |
  • 84 | 85 | 用户头像 86 | 87 |
    88 |
    89 | 落基山脉下的闲人 90 | 91 | 关注于 2019-04-28 14:13:25 92 | 93 |
    94 |
    95 | 96 | 98 |
    99 |
    100 |
  • 101 |
102 | 103 | 116 |
117 |
118 | 119 | 120 |
121 |
122 |
123 | 124 |
125 | 126 |
127 | 128 |
129 |
130 |
131 | 154 |
155 |
156 |
157 |
158 | 174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | -------------------------------------------------------------------------------- /src/main/java/com/nowcoder/community/service/UserService.java: -------------------------------------------------------------------------------- 1 | package com.nowcoder.community.service; 2 | 3 | import com.nowcoder.community.dao.UserMapper; 4 | import com.nowcoder.community.entity.LoginTicket; 5 | import com.nowcoder.community.entity.User; 6 | import com.nowcoder.community.utils.CommunityConstant; 7 | import com.nowcoder.community.utils.CommunityUtil; 8 | import com.nowcoder.community.utils.MailClient; 9 | import com.nowcoder.community.utils.RedisKeyUtil; 10 | import org.apache.commons.lang3.StringUtils; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.beans.factory.annotation.Value; 13 | import org.springframework.data.redis.core.RedisTemplate; 14 | import org.springframework.stereotype.Service; 15 | import org.thymeleaf.TemplateEngine; 16 | import org.thymeleaf.context.Context; 17 | 18 | import java.util.Date; 19 | import java.util.HashMap; 20 | import java.util.Map; 21 | import java.util.concurrent.TimeUnit; 22 | 23 | @Service 24 | public class UserService implements CommunityConstant { 25 | 26 | @Autowired 27 | private UserMapper userMapper; 28 | 29 | @Autowired 30 | private MailClient mailClient; 31 | 32 | @Autowired 33 | private TemplateEngine templateEngine; 34 | 35 | @Autowired 36 | private RedisTemplate redisTemplate; 37 | /** 38 | * 域名 39 | */ 40 | @Value("${community.path.domain}") 41 | private String domain; 42 | 43 | /** 44 | * 项目名 45 | */ 46 | @Value("${server.servlet.context-path}") 47 | private String contextPath; 48 | 49 | public User findUserById(int id) { 50 | User user = getCache(id); 51 | if(user == null){ 52 | user = initCache(id); 53 | } 54 | return user; 55 | } 56 | 57 | /** 58 | * 注册 59 | * @param user 60 | * @return 61 | */ 62 | public Map register(User user){ 63 | HashMap map = new HashMap<>(); 64 | 65 | if(user == null){ 66 | throw new IllegalArgumentException("user不能为空"); 67 | } 68 | if(StringUtils.isBlank(user.getUsername())){ 69 | map.put("usernameMsg","账号不能为空! "); 70 | return map; 71 | } 72 | if(StringUtils.isBlank(user.getPassword())){ 73 | map.put("passwordMsg","密码不能为空! "); 74 | return map; 75 | } 76 | if(StringUtils.isBlank(user.getEmail())){ 77 | map.put("emailMsg","邮箱不能为空! "); 78 | return map; 79 | } 80 | 81 | //验证账号 82 | User u = userMapper.selectByName(user.getUsername()); 83 | if(u != null){ 84 | map.put("usernameMsg","该账号已存在!"); 85 | return map; 86 | } 87 | 88 | //验证邮箱 89 | u = userMapper.selectByEmail(user.getEmail()); 90 | if(u != null){ 91 | map.put("emailMsg","该邮箱已被注册!"); 92 | return map; 93 | } 94 | 95 | //注册用户 96 | user.setSalt(CommunityUtil.generateUUID().substring(0,5)); 97 | user.setPassword(CommunityUtil.md5(user.getPassword()+user.getSalt())); 98 | user.setType(0); 99 | user.setStatus(0); 100 | user.setActivationCode(CommunityUtil.generateUUID()); 101 | user.setHeaderUrl(String.format("C:/Users/30669/Desktop/head.PNG")); 102 | user.setCreateTime(new Date()); 103 | userMapper.insertUser(user); 104 | 105 | //给用户发送激活邮件 106 | Context context = new Context(); 107 | context.setVariable("email",user.getEmail()); 108 | 109 | //http:localhost:8080/community/activation/101/code 110 | String url = domain + contextPath + "/activation/" + user.getId() + "/" + user.getActivationCode(); 111 | context.setVariable("url",url); 112 | String content = templateEngine.process("/mail/activation",context); 113 | mailClient.sendMail(user.getEmail(),"激活账号",content); 114 | 115 | return map; 116 | } 117 | 118 | /** 119 | * 判断是否激活 120 | * @param userId 121 | * @param code 122 | * @return 123 | */ 124 | public int activation(int userId, String code){ 125 | User user = userMapper.selectById(userId); 126 | if(user.getStatus() == 1){ 127 | return ACTIVATION_SUCCESS; 128 | }else if(user.getActivationCode().equals(code)){ 129 | userMapper.updateStatus(userId, 1); 130 | clearCache(userId); 131 | }else{ 132 | return ACTIVATION_FAILURE; 133 | } 134 | return 0; 135 | } 136 | 137 | /** 138 | * 登录功能,设置登录凭证,检查账号信息 139 | * @param username 140 | * @param password 141 | * @param expired 设置过期时间 142 | * @return 143 | */ 144 | public Map login(String username, String password, int expired){ 145 | Map map = new HashMap<>(); 146 | if(StringUtils.isBlank(username)){ 147 | map.put("usernameMsg","账号不能为空!"); 148 | return map; 149 | } 150 | if(StringUtils.isBlank(password)){ 151 | map.put("passwordMsg","密码不能为空!"); 152 | return map; 153 | } 154 | 155 | //验证账号 156 | User user = userMapper.selectByName(username); 157 | if(user == null){ 158 | map.put("usernameMsg","该账号不存在!"); 159 | return map; 160 | } 161 | 162 | //验证激活状态 163 | if(user.getStatus() == 0){ 164 | map.put("usernameMsg","该账号未激活!"); 165 | return map; 166 | } 167 | 168 | //验证密码 169 | String s = CommunityUtil.md5(password + user.getSalt()); 170 | if(!s.equals(user.getPassword())){ 171 | map.put("passwordMsg","密码错误!"); 172 | return map; 173 | } 174 | 175 | //生成登录凭证 176 | LoginTicket loginTicket = new LoginTicket(); 177 | loginTicket.setUserId(user.getId()); 178 | loginTicket.setStatus(0); 179 | loginTicket.setTicket(CommunityUtil.generateUUID()); 180 | loginTicket.setExpired(new Date(System.currentTimeMillis()+expired*1000)); 181 | 182 | //存入redis 183 | String ticketKey = RedisKeyUtil.getTicketKey(loginTicket.getTicket()); 184 | redisTemplate.opsForValue().set(ticketKey, loginTicket); 185 | 186 | 187 | map.put("ticket",loginTicket.getTicket()); 188 | 189 | return map; 190 | } 191 | 192 | /** 193 | * 退出登录 194 | * @param loginTicket 195 | */ 196 | public void logout(String loginTicket){ 197 | String ticketKey = RedisKeyUtil.getTicketKey(loginTicket); 198 | LoginTicket loginTicket1 = (LoginTicket) redisTemplate.opsForValue().get(ticketKey); 199 | loginTicket1.setStatus(1); 200 | redisTemplate.opsForValue().set(ticketKey,loginTicket1); 201 | } 202 | 203 | /** 204 | * 获取登录凭证对象 205 | * @param ticket 206 | * @return 207 | */ 208 | public LoginTicket findLoginTicket(String ticket){ 209 | String ticketKey = RedisKeyUtil.getTicketKey(ticket); 210 | return (LoginTicket)redisTemplate.opsForValue().get(ticketKey); 211 | 212 | } 213 | 214 | /** 215 | * 上传头像 216 | * @param userId 217 | * @param headUrl 218 | * @return 219 | */ 220 | public int updateHeader(int userId, String headUrl){ 221 | int rows = userMapper.updateHeader(userId, headUrl); 222 | clearCache(userId); 223 | return rows; 224 | } 225 | 226 | 227 | public int updatePassword(int userId, String password){ 228 | return userMapper.updatePassword(userId, password); 229 | } 230 | 231 | public User findUserByName(String username) { 232 | return userMapper.selectByName(username); 233 | } 234 | 235 | // 1.优先从缓存中取值 236 | private User getCache(int userId){ 237 | String userKey = RedisKeyUtil.getUserKey(userId); 238 | return (User)redisTemplate.opsForValue().get(userKey); 239 | } 240 | // 2.取不到时初始化缓存数据 241 | private User initCache(int userId){ 242 | User user = userMapper.selectById(userId); 243 | String userKey = RedisKeyUtil.getUserKey(userId); 244 | redisTemplate.opsForValue().set(userKey,user,3600, TimeUnit.SECONDS); 245 | return user; 246 | } 247 | // 3.数据变更时清除缓存数据 248 | private void clearCache(int userId){ 249 | String userKey = RedisKeyUtil.getUserKey(userId); 250 | redisTemplate.delete(userKey); 251 | } 252 | } 253 | --------------------------------------------------------------------------------