├── .gitignore ├── .mvn └── wrapper │ ├── MavenWrapperDownloader.java │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── LICENSE ├── README.md ├── mvnw ├── mvnw.cmd ├── pom.xml ├── sql └── init.sql └── src ├── main ├── java │ └── com │ │ └── community │ │ ├── CommunityApplication.java │ │ ├── actuator │ │ └── DatabaseEndpoint.java │ │ ├── annotation │ │ └── LoginRequired.java │ │ ├── aspect │ │ ├── AlphaAspect.java │ │ └── ServiceLogAspect.java │ │ ├── config │ │ ├── AlphaConfig.java │ │ ├── KaptchaConfig.java │ │ ├── QuartzConfig.java │ │ ├── RedisConfig.java │ │ ├── SecurityConfig.java │ │ ├── ThreadPoolConfig.java │ │ ├── WebMvcConfig.java │ │ └── WkConfig.java │ │ ├── controller │ │ ├── AlphaController.java │ │ ├── CommentController.java │ │ ├── DataController.java │ │ ├── DiscussPostController.java │ │ ├── FollowController.java │ │ ├── HomeController.java │ │ ├── LikeController.java │ │ ├── LoginController.java │ │ ├── MessageController.java │ │ ├── SearchController.java │ │ ├── ShareController.java │ │ ├── UserController.java │ │ ├── advice │ │ │ └── ExceptionAdvice.java │ │ └── interceptor │ │ │ ├── AlphaInterceptor.java │ │ │ ├── DataInterceptor.java │ │ │ ├── LoginRequiredInterceptor.java │ │ │ ├── LoginTicketInterceptor.java │ │ │ └── MessageInterceptor.java │ │ ├── dao │ │ ├── AlphaDao.java │ │ ├── AlphaDaoHibernateImpl.java │ │ ├── AlphaDaoMyBatisImpl.java │ │ ├── CommentMapper.java │ │ ├── DiscussPostMapper.java │ │ ├── LoginTicketMapper.java │ │ ├── MessageMapper.java │ │ ├── UserMapper.java │ │ └── elasticsearch │ │ │ └── DiscussPostRepository.java │ │ ├── entity │ │ ├── Comment.java │ │ ├── DiscussPost.java │ │ ├── Event.java │ │ ├── LoginTicket.java │ │ ├── Message.java │ │ ├── Page.java │ │ └── User.java │ │ ├── event │ │ ├── EventConsumer.java │ │ └── EventProducer.java │ │ ├── quartz │ │ ├── AlphaJob.java │ │ └── PostScoreRefreshJob.java │ │ ├── service │ │ ├── AlphaService.java │ │ ├── CommentService.java │ │ ├── DataService.java │ │ ├── DiscussPostService.java │ │ ├── ElasticsearchService.java │ │ ├── FollowService.java │ │ ├── LikeService.java │ │ ├── MessageService.java │ │ └── UserService.java │ │ └── util │ │ ├── CommunityConstant.java │ │ ├── CommunityUtil.java │ │ ├── CookieUtil.java │ │ ├── HostHolder.java │ │ ├── MailClient.java │ │ ├── RedisKeyUtil.java │ │ └── SensitiveFilter.java └── resources │ ├── application-dev.properties │ ├── application-pro.properties │ ├── application.properties │ ├── logback-spring-dev.xml │ ├── logback-spring-pro.xml │ ├── mapper │ ├── comment-mapper.xml │ ├── discusspost-mapper.xml │ ├── message-mapper.xml │ └── user-mapper.xml │ ├── sensitive-words.txt │ ├── static │ ├── css │ │ ├── discuss-detail.css │ │ ├── global.css │ │ ├── letter.css │ │ └── login.css │ ├── html │ │ ├── ajax-demo.html │ │ └── student.html │ ├── img │ │ ├── 404.png │ │ ├── error.png │ │ └── icon.png │ └── js │ │ ├── discuss.js │ │ ├── forget.js │ │ ├── global.js │ │ ├── index.js │ │ ├── letter.js │ │ ├── profile.js │ │ └── setting.js │ └── templates │ ├── demo │ └── view.html │ ├── error │ ├── 404.html │ └── 500.html │ ├── index.html │ ├── mail │ ├── activation.html │ ├── demo.html │ └── forget.html │ └── site │ ├── admin │ └── data.html │ ├── discuss-detail.html │ ├── followee.html │ ├── follower.html │ ├── forget.html │ ├── letter-detail.html │ ├── letter.html │ ├── login.html │ ├── my-comment.html │ ├── my-post.html │ ├── notice-detail.html │ ├── notice.html │ ├── operate-result.html │ ├── operate.html │ ├── profile.html │ ├── register.html │ ├── search.html │ └── setting.html └── test └── java └── com └── community ├── BlockingQueueTests.java ├── CommunityApplicationTests.java ├── ElasticsearchTests.java ├── KafkaTests.java ├── LoggerTests.java ├── MailTests.java ├── MapperTests.java ├── QuartzTests.java ├── RedisTests.java ├── SensitiveTests.java ├── SortTests.java ├── ThreadPoolTests.java ├── TransactionTests.java └── WkTests.java /.gitignore: -------------------------------------------------------------------------------- 1 | ### IDEA ### 2 | .idea/ 3 | *.iml 4 | */target/ 5 | target/ 6 | */*.iml 7 | */.gradle/ 8 | */out/ 9 | out/ 10 | ### Java ### 11 | .settings/ 12 | .classpath 13 | .project 14 | bin/ 15 | build/ -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuumiy/community/c76d7445f2f47867c2ee9124eb331c224be5ae0b/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 David 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 基于SpringBoot的学习社区 2 | 3 | #### 1、项目环境 4 | 5 | SpringBoot 2.1.5.RELEASE 6 | 7 | Maven 3.5.2 8 | 9 | Tomcat 8 10 | 11 | jdk1.8 12 | 13 | #### 2、技术栈 14 | 15 | 技术栈:Spring+Springmvc+Mybatis+SpringBoot+Mysql+Redis+Thymeleaf+Kafka+ElasticSearch+Quartz+Caffine 16 | 17 | #### 3、项目启动方式 18 | 19 | 配置mysql、七牛云等信息。 20 | 21 | 打开zookeeper、kafka、elasticsearch、redis。 22 | 23 | 24 | 25 | F:\JavaTools\redis-2.8.9>redis-server.exe redis.windows.conf 26 | 27 | F:\JavaTools\kafka_2.12-2.3.0>bin\windows\zookeeper-server-start.bat config\zookeeper.properties 28 | 29 | F:\JavaTools\kafka_2.12-2.3.0>bin\windows\kafka-server-start.bat config\server.properties 30 | 31 | 打开es的bin目录,打开es.bat 32 | 33 | 开发环境使用application-dev,生产环境使用application-pro,生产环境需要重新配置文件目录地址 34 | 35 | #### 4、提供的账号 36 | 普通用户:aaa  密码:123456   无权限 37 | 38 | 版主:bbb   密码:123456   置顶、加精权限 39 | 40 | 管理员:ccc   密码:123456   置顶、加精、删帖权限 -------------------------------------------------------------------------------- /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 | 12 | 13 | com.community 14 | community 15 | 0.0.1-SNAPSHOT 16 | community 17 | Community for Spring Boot 18 | 19 | 20 | 21 | 1.8 22 | 23 | 24 | 25 | 26 | org.springframework.boot 27 | spring-boot-starter-aop 28 | 29 | 30 | org.springframework.boot 31 | spring-boot-starter-thymeleaf 32 | 33 | 34 | org.springframework.boot 35 | spring-boot-starter-web 36 | 37 | 38 | 39 | org.springframework.boot 40 | spring-boot-devtools 41 | runtime 42 | 43 | 44 | org.springframework.boot 45 | spring-boot-starter-test 46 | test 47 | 48 | 49 | 50 | mysql 51 | mysql-connector-java 52 | 8.0.16 53 | 54 | 55 | org.mybatis.spring.boot 56 | mybatis-spring-boot-starter 57 | 2.0.1 58 | 59 | 60 | 61 | org.springframework.boot 62 | spring-boot-starter-mail 63 | 2.1.5.RELEASE 64 | 65 | 66 | 67 | 68 | org.apache.commons 69 | commons-lang3 70 | 3.9 71 | 72 | 73 | 74 | com.github.penggle 75 | kaptcha 76 | 2.3.2 77 | 78 | 79 | 80 | 81 | com.alibaba 82 | fastjson 83 | 1.2.58 84 | 85 | 86 | 87 | org.springframework.boot 88 | spring-boot-starter-data-redis 89 | 90 | 91 | 92 | org.springframework.kafka 93 | spring-kafka 94 | 95 | 96 | 97 | org.springframework.boot 98 | spring-boot-starter-data-elasticsearch 99 | 100 | 101 | 102 | org.springframework.boot 103 | spring-boot-starter-security 104 | 105 | 106 | 107 | org.thymeleaf.extras 108 | thymeleaf-extras-springsecurity5 109 | 110 | 111 | 112 | org.springframework.boot 113 | spring-boot-starter-quartz 114 | 115 | 116 | 117 | com.qiniu 118 | qiniu-java-sdk 119 | 7.2.23 120 | 121 | 122 | 123 | com.github.ben-manes.caffeine 124 | caffeine 125 | 2.7.0 126 | 127 | 128 | 129 | org.springframework.boot 130 | spring-boot-starter-actuator 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | org.springframework.boot 139 | spring-boot-maven-plugin 140 | 141 | 142 | 143 | 144 | 145 | -------------------------------------------------------------------------------- /src/main/java/com/community/CommunityApplication.java: -------------------------------------------------------------------------------- 1 | package com.community; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | import javax.annotation.PostConstruct; 7 | 8 | @SpringBootApplication 9 | public class CommunityApplication { 10 | 11 | @PostConstruct 12 | public void init() { 13 | /*Netty 提供异步的、基于事件驱动的网络应用程序框架,用以快速开发高性能、高可靠性的网络 IO 程序, 14 | 是目前最流行的 NIO 框架,Netty 在互联网领域、大数据分布式计算领域、游戏行业、通信行业等获得了广泛的应用, 15 | 知名的 Dubbo 、RocketMQ 框架内部都采用了 Netty。*/ 16 | // 解决netty启动冲突问题 17 | // see Netty4Utils.setAvailableProcessors() 18 | System.setProperty("es.set.netty.runtime.available.processors", "false"); 19 | } 20 | 21 | public static void main(String[] args) { 22 | SpringApplication.run(CommunityApplication.class, args); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/community/actuator/DatabaseEndpoint.java: -------------------------------------------------------------------------------- 1 | package com.community.actuator; 2 | 3 | import com.community.util.CommunityUtil; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.actuate.endpoint.annotation.Endpoint; 8 | import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; 9 | import org.springframework.stereotype.Component; 10 | 11 | import javax.sql.DataSource; 12 | import java.sql.Connection; 13 | import java.sql.SQLException; 14 | 15 | //@Component使用容器管理,用@Autowired获取对象 16 | @Component 17 | @Endpoint(id = "database") 18 | public class DatabaseEndpoint { 19 | 20 | private static final Logger logger = LoggerFactory.getLogger(DatabaseEndpoint.class); 21 | 22 | //连接池由Spring进行管理,直接注入进来即可 23 | @Autowired 24 | private DataSource dataSource; 25 | 26 | @ReadOperation 27 | public String checkConnection() { 28 | //加在小括号里的资源,结束的时候会自动关闭 29 | try ( 30 | Connection conn = dataSource.getConnection(); 31 | ) { 32 | return CommunityUtil.getJSONString(0, "获取连接成功!"); 33 | } catch (SQLException e) { 34 | logger.error("获取连接失败:" + e.getMessage()); 35 | return CommunityUtil.getJSONString(1, "获取连接失败!"); 36 | } 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/community/annotation/LoginRequired.java: -------------------------------------------------------------------------------- 1 | package com.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 | @Target(ElementType.METHOD) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface LoginRequired { 11 | 12 | 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/community/aspect/AlphaAspect.java: -------------------------------------------------------------------------------- 1 | package com.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 | //对所有的业务组件统一处理 12 | @Pointcut("execution(* com.community.service.*.*(..))") 13 | //切点是service包下所有的方法 14 | public void pointcut() { 15 | 16 | } 17 | 18 | @Before("pointcut()") 19 | public void before() { 20 | System.out.println("before"); 21 | } 22 | 23 | @After("pointcut()") 24 | public void after() { 25 | System.out.println("after"); 26 | } 27 | 28 | @AfterReturning("pointcut()") 29 | public void afterRetuning() { 30 | System.out.println("afterRetuning"); 31 | } 32 | 33 | @AfterThrowing("pointcut()") 34 | public void afterThrowing() { 35 | System.out.println("afterThrowing"); 36 | } 37 | 38 | @Around("pointcut()") 39 | public Object around(ProceedingJoinPoint joinPoint) throws Throwable { 40 | System.out.println("around before"); 41 | Object obj = joinPoint.proceed(); 42 | System.out.println("around after"); 43 | return obj; 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/com/community/aspect/ServiceLogAspect.java: -------------------------------------------------------------------------------- 1 | package com.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 | //这是统一日志处理的类,利用AOP切面技术,在访问service方法时会执行pointcut。定义了一个切面类 20 | public class ServiceLogAspect { 21 | 22 | private static final Logger logger = LoggerFactory.getLogger(ServiceLogAspect.class); 23 | 24 | @Pointcut("execution(* com.community.service.*.*(..))") 25 | public void pointcut() { 26 | 27 | } 28 | 29 | @Before("pointcut()") 30 | public void before(JoinPoint joinPoint) { 31 | // 用户[1.2.3.4],在[xxx],访问了[com.community.service.xxx()]. 32 | ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); 33 | if (attributes == null) { 34 | return; 35 | } 36 | HttpServletRequest request = attributes.getRequest(); 37 | String ip = request.getRemoteHost(); 38 | String now = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()); 39 | String target = joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName(); 40 | logger.info(String.format("用户[%s],在[%s],访问了[%s].", ip, now, target)); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/community/config/AlphaConfig.java: -------------------------------------------------------------------------------- 1 | package com.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/java/com/community/config/KaptchaConfig.java: -------------------------------------------------------------------------------- 1 | package com.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.Properties; 10 | 11 | @Configuration 12 | public class KaptchaConfig { 13 | 14 | @Bean 15 | public Producer kaptchaProducer() { 16 | Properties properties = new Properties(); 17 | properties.setProperty("kaptcha.image.width", "100"); 18 | properties.setProperty("kaptcha.image.height", "40"); 19 | properties.setProperty("kaptcha.textproducer.font.size", "32"); 20 | properties.setProperty("kaptcha.textproducer.font.color", "0,0,0"); 21 | properties.setProperty("kaptcha.textproducer.char.string", "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYAZ"); 22 | properties.setProperty("kaptcha.textproducer.char.length", "4"); 23 | properties.setProperty("kaptcha.noise.impl", "com.google.code.kaptcha.impl.NoNoise"); 24 | 25 | DefaultKaptcha kaptcha = new DefaultKaptcha(); 26 | Config config = new Config(properties); 27 | kaptcha.setConfig(config); 28 | return kaptcha; 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/community/config/QuartzConfig.java: -------------------------------------------------------------------------------- 1 | package com.community.config; 2 | 3 | import com.community.quartz.PostScoreRefreshJob; 4 | import com.community.quartz.AlphaJob; 5 | import org.quartz.JobDataMap; 6 | import org.quartz.JobDetail; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.context.annotation.Configuration; 9 | import org.springframework.scheduling.quartz.JobDetailFactoryBean; 10 | import org.springframework.scheduling.quartz.SimpleTriggerFactoryBean; 11 | 12 | // 配置 -> 数据库 -> 调用 13 | @Configuration 14 | public class QuartzConfig { 15 | 16 | // FactoryBean可简化Bean的实例化过程: 17 | // 1.通过FactoryBean封装Bean的实例化过程. 18 | // 2.将FactoryBean装配到Spring容器里. 19 | // 3.将FactoryBean注入给其他的Bean. 20 | // 4.该Bean得到的是FactoryBean所管理的对象实例. 21 | 22 | // 配置JobDetail @Bean表示程序一启动就会被初始化,因此就会执行调度任务。我们不想让调度任务启动,注释即可 23 | // @Bean 24 | public JobDetailFactoryBean alphaJobDetail() { 25 | JobDetailFactoryBean factoryBean = new JobDetailFactoryBean(); 26 | factoryBean.setJobClass(AlphaJob.class); 27 | factoryBean.setName("alphaJob"); 28 | factoryBean.setGroup("alphaJobGroup"); 29 | factoryBean.setDurability(true); 30 | factoryBean.setRequestsRecovery(true); 31 | return factoryBean; 32 | } 33 | 34 | // 配置Trigger(SimpleTriggerFactoryBean, CronTriggerFactoryBean). 35 | //每3s调度一次任务 36 | // @Bean 37 | public SimpleTriggerFactoryBean alphaTrigger(JobDetail alphaJobDetail) { 38 | SimpleTriggerFactoryBean factoryBean = new SimpleTriggerFactoryBean(); 39 | factoryBean.setJobDetail(alphaJobDetail); 40 | factoryBean.setName("alphaTrigger"); 41 | factoryBean.setGroup("alphaTriggerGroup"); 42 | factoryBean.setRepeatInterval(3000); 43 | factoryBean.setJobDataMap(new JobDataMap()); 44 | return factoryBean; 45 | } 46 | 47 | // 刷新帖子分数任务 48 | @Bean 49 | public JobDetailFactoryBean postScoreRefreshJobDetail() { 50 | JobDetailFactoryBean factoryBean = new JobDetailFactoryBean(); 51 | factoryBean.setJobClass(PostScoreRefreshJob.class); 52 | factoryBean.setName("postScoreRefreshJob"); 53 | factoryBean.setGroup("communityJobGroup"); 54 | factoryBean.setDurability(true); 55 | factoryBean.setRequestsRecovery(true); 56 | return factoryBean; 57 | } 58 | 59 | //设置每2小时执行一次 60 | @Bean 61 | public SimpleTriggerFactoryBean postScoreRefreshTrigger(JobDetail postScoreRefreshJobDetail) { 62 | SimpleTriggerFactoryBean factoryBean = new SimpleTriggerFactoryBean(); 63 | factoryBean.setJobDetail(postScoreRefreshJobDetail); 64 | factoryBean.setName("postScoreRefreshTrigger"); 65 | factoryBean.setGroup("communityTriggerGroup"); 66 | factoryBean.setRepeatInterval(1000 * 60 * 60 * 2); //2小时执行一次 67 | factoryBean.setJobDataMap(new JobDataMap()); 68 | return factoryBean; 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/com/community/config/RedisConfig.java: -------------------------------------------------------------------------------- 1 | package com.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 | @Configuration 10 | public class RedisConfig { 11 | 12 | @Bean 13 | //@Bean的作用是:在服务启动时,将RedisTemplate交给spring管理 14 | public RedisTemplate redisTemplate(RedisConnectionFactory factory) { 15 | RedisTemplate template = new RedisTemplate<>(); 16 | template.setConnectionFactory(factory); 17 | 18 | // 设置key的序列化方式 19 | template.setKeySerializer(RedisSerializer.string()); 20 | // 设置value的序列化方式 21 | template.setValueSerializer(RedisSerializer.json()); 22 | // 设置hash的key的序列化方式 23 | template.setHashKeySerializer(RedisSerializer.string()); 24 | // 设置hash的value的序列化方式 25 | template.setHashValueSerializer(RedisSerializer.json()); 26 | 27 | template.afterPropertiesSet(); 28 | return template; 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/community/config/ThreadPoolConfig.java: -------------------------------------------------------------------------------- 1 | package com.community.config; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.scheduling.annotation.EnableAsync; 5 | import org.springframework.scheduling.annotation.EnableScheduling; 6 | 7 | //这些注解表示启用了Spring的线程池,并且开启定时任务 8 | @Configuration 9 | @EnableScheduling 10 | @EnableAsync 11 | public class ThreadPoolConfig { 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/community/config/WebMvcConfig.java: -------------------------------------------------------------------------------- 1 | package com.community.config; 2 | 3 | import com.community.controller.interceptor.AlphaInterceptor; 4 | import com.community.controller.interceptor.DataInterceptor; 5 | import com.community.controller.interceptor.LoginTicketInterceptor; 6 | import com.community.controller.interceptor.MessageInterceptor; 7 | import com.community.controller.interceptor.*; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.context.annotation.Configuration; 10 | import org.springframework.web.servlet.config.annotation.InterceptorRegistry; 11 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 12 | 13 | @Configuration 14 | public class WebMvcConfig implements WebMvcConfigurer { 15 | 16 | @Autowired 17 | private AlphaInterceptor alphaInterceptor; 18 | 19 | @Autowired 20 | private LoginTicketInterceptor loginTicketInterceptor; 21 | 22 | // @Autowired 23 | // private LoginRequiredInterceptor loginRequiredInterceptor; 24 | 25 | @Autowired 26 | private MessageInterceptor messageInterceptor; 27 | 28 | @Autowired 29 | private DataInterceptor dataInterceptor; 30 | 31 | @Override 32 | public void addInterceptors(InterceptorRegistry registry) { 33 | registry.addInterceptor(alphaInterceptor) 34 | .excludePathPatterns("/**/*.css", "/**/*.js", "/**/*.png", "/**/*.jpg", "/**/*.jpeg") 35 | .addPathPatterns("/register", "/login"); 36 | 37 | //所有请求都要拦截,除了static下的静态资源不拦截 38 | registry.addInterceptor(loginTicketInterceptor) 39 | .excludePathPatterns("/**/*.css", "/**/*.js", "/**/*.png", "/**/*.jpg", "/**/*.jpeg"); 40 | 41 | // registry.addInterceptor(loginRequiredInterceptor) 42 | // .excludePathPatterns("/**/*.css", "/**/*.js", "/**/*.png", "/**/*.jpg", "/**/*.jpeg"); 43 | 44 | registry.addInterceptor(messageInterceptor) 45 | .excludePathPatterns("/**/*.css", "/**/*.js", "/**/*.png", "/**/*.jpg", "/**/*.jpeg"); 46 | 47 | registry.addInterceptor(dataInterceptor) 48 | .excludePathPatterns("/**/*.css", "/**/*.js", "/**/*.png", "/**/*.jpg", "/**/*.jpeg"); 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/com/community/config/WkConfig.java: -------------------------------------------------------------------------------- 1 | package com.community.config; 2 | 3 | 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.springframework.beans.factory.annotation.Value; 7 | import org.springframework.context.annotation.Configuration; 8 | 9 | import javax.annotation.PostConstruct; 10 | import java.io.File; 11 | 12 | @Configuration 13 | public class WkConfig { 14 | 15 | private static final Logger logger = LoggerFactory.getLogger(WkConfig.class); 16 | 17 | @Value("${wk.image.storage}") 18 | private String wkImageStorage; 19 | 20 | @PostConstruct //这个注解表示启动服务的时候,init()方法会调用一次 21 | public void init() { 22 | // 创建WK图片目录 23 | File file = new File(wkImageStorage); 24 | if (!file.exists()) { 25 | file.mkdir(); 26 | logger.info("创建WK图片目录: " + wkImageStorage); 27 | } 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/community/controller/CommentController.java: -------------------------------------------------------------------------------- 1 | package com.community.controller; 2 | 3 | import com.community.entity.DiscussPost; 4 | import com.community.util.HostHolder; 5 | import com.community.entity.Comment; 6 | import com.community.entity.Event; 7 | import com.community.event.EventProducer; 8 | import com.community.service.CommentService; 9 | import com.community.service.DiscussPostService; 10 | import com.community.util.CommunityConstant; 11 | import com.community.util.RedisKeyUtil; 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | import org.springframework.data.redis.core.RedisTemplate; 14 | import org.springframework.stereotype.Controller; 15 | import org.springframework.web.bind.annotation.PathVariable; 16 | import org.springframework.web.bind.annotation.RequestMapping; 17 | import org.springframework.web.bind.annotation.RequestMethod; 18 | 19 | import java.util.Date; 20 | 21 | @Controller 22 | @RequestMapping("/comment") 23 | public class CommentController implements CommunityConstant { 24 | 25 | @Autowired 26 | private CommentService commentService; 27 | 28 | @Autowired 29 | private HostHolder hostHolder; 30 | 31 | @Autowired 32 | private EventProducer eventProducer; 33 | 34 | @Autowired 35 | private DiscussPostService discussPostService; 36 | 37 | @Autowired 38 | private RedisTemplate redisTemplate; 39 | 40 | @RequestMapping(path = "/add/{discussPostId}", method = RequestMethod.POST) 41 | public String addComment(@PathVariable("discussPostId") int discussPostId, Comment comment) { 42 | //Comment的其他属性,form表单提交时就会赋值。有些属性可以写在中,这样就可以赋值 43 | comment.setUserId(hostHolder.getUser().getId()); 44 | comment.setStatus(0); 45 | comment.setCreateTime(new Date()); 46 | commentService.addComment(comment); 47 | 48 | //触发评论事件 49 | Event event = new Event() 50 | .setTopic(TOPIC_COMMENT) 51 | .setUserId(hostHolder.getUser().getId()) 52 | .setEntityType(comment.getEntityType()) 53 | .setEntityId(comment.getEntityId()) 54 | .setData("postId", discussPostId); 55 | if (comment.getEntityType() == ENTITY_TYPE_POST) { 56 | DiscussPost target = discussPostService.findDiscussPostById(comment.getEntityId()); 57 | event.setEntityUserId(target.getUserId()); 58 | } else if (comment.getEntityType() == ENTITY_TYPE_COMMENT) { 59 | Comment target = commentService.findCommentById(comment.getEntityId()); 60 | event.setEntityUserId(target.getUserId()); 61 | } 62 | eventProducer.fireEvent(event); 63 | 64 | //触发评论事件后,还要触发发帖事件,因为帖子评论数量改变 65 | if (comment.getEntityType() == ENTITY_TYPE_POST) { 66 | // 触发发帖事件 67 | event = new Event() 68 | .setTopic(TOPIC_PUBLISH) 69 | .setUserId(comment.getUserId()) 70 | .setEntityType(ENTITY_TYPE_POST) 71 | .setEntityId(discussPostId); 72 | eventProducer.fireEvent(event); 73 | 74 | // 计算帖子分数 75 | String redisKey = RedisKeyUtil.getPostScoreKey(); 76 | redisTemplate.opsForSet().add(redisKey, discussPostId); 77 | } 78 | 79 | return "redirect:/discuss/detail/" + discussPostId; 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/com/community/controller/DataController.java: -------------------------------------------------------------------------------- 1 | package com.community.controller; 2 | 3 | import com.community.service.DataService; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.format.annotation.DateTimeFormat; 6 | import org.springframework.stereotype.Controller; 7 | import org.springframework.ui.Model; 8 | import org.springframework.web.bind.annotation.RequestMapping; 9 | import org.springframework.web.bind.annotation.RequestMethod; 10 | 11 | import java.util.Date; 12 | 13 | @Controller 14 | public class DataController { 15 | 16 | @Autowired 17 | private DataService dataService; 18 | 19 | // 统计页面,既可以处理Get请求,也可以处理Post请求 20 | @RequestMapping(path = "/data", method = {RequestMethod.GET, RequestMethod.POST}) 21 | public String getDataPage() { 22 | return "/site/admin/data"; 23 | } 24 | 25 | // 统计网站UV @DateTimeFormat将字符串格式的日期转为了日期格式 26 | @RequestMapping(path = "/data/uv", method = RequestMethod.POST) 27 | public String getUV(@DateTimeFormat(pattern = "yyyy-MM-dd") Date start, 28 | @DateTimeFormat(pattern = "yyyy-MM-dd") Date end, Model model) { 29 | long uv = dataService.calculateUV(start, end); 30 | model.addAttribute("uvResult", uv); 31 | model.addAttribute("uvStartDate", start); 32 | model.addAttribute("uvEndDate", end); 33 | //forward相当于一个转发的请求 34 | return "forward:/data"; 35 | } 36 | 37 | // 统计活跃用户。日活跃用户数DAU 38 | @RequestMapping(path = "/data/dau", method = RequestMethod.POST) 39 | public String getDAU(@DateTimeFormat(pattern = "yyyy-MM-dd") Date start, 40 | @DateTimeFormat(pattern = "yyyy-MM-dd") Date end, Model model) { 41 | long dau = dataService.calculateDAU(start, end); 42 | model.addAttribute("dauResult", dau); 43 | model.addAttribute("dauStartDate", start); 44 | model.addAttribute("dauEndDate", end); 45 | return "forward:/data"; 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/com/community/controller/FollowController.java: -------------------------------------------------------------------------------- 1 | package com.community.controller; 2 | 3 | import com.community.entity.Page; 4 | import com.community.util.CommunityUtil; 5 | import com.community.util.HostHolder; 6 | import com.community.entity.Event; 7 | import com.community.entity.User; 8 | import com.community.event.EventProducer; 9 | import com.community.service.FollowService; 10 | import com.community.service.UserService; 11 | import com.community.util.CommunityConstant; 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | import org.springframework.stereotype.Controller; 14 | import org.springframework.ui.Model; 15 | import org.springframework.web.bind.annotation.PathVariable; 16 | import org.springframework.web.bind.annotation.RequestMapping; 17 | import org.springframework.web.bind.annotation.RequestMethod; 18 | import org.springframework.web.bind.annotation.ResponseBody; 19 | 20 | import java.util.List; 21 | import java.util.Map; 22 | 23 | @Controller 24 | public class FollowController implements CommunityConstant { 25 | 26 | @Autowired 27 | private FollowService followService; 28 | 29 | @Autowired 30 | private HostHolder hostHolder; 31 | 32 | @Autowired 33 | private UserService userService; 34 | 35 | @Autowired 36 | private EventProducer eventProducer; 37 | 38 | @RequestMapping(path = "/follow", method = RequestMethod.POST) 39 | @ResponseBody 40 | public String follow(int entityType, int entityId) { 41 | User user = hostHolder.getUser(); 42 | 43 | followService.follow(user.getId(), entityType, entityId); 44 | 45 | // 触发关注事件 46 | Event event = new Event() 47 | .setTopic(TOPIC_FOLLOW) 48 | .setUserId(hostHolder.getUser().getId()) 49 | .setEntityType(entityType) 50 | .setEntityId(entityId) 51 | .setEntityUserId(entityId); 52 | eventProducer.fireEvent(event); 53 | 54 | return CommunityUtil.getJSONString(0, "已关注!"); 55 | } 56 | 57 | @RequestMapping(path = "/unfollow", method = RequestMethod.POST) 58 | @ResponseBody 59 | public String unfollow(int entityType, int entityId) { 60 | User user = hostHolder.getUser(); 61 | 62 | followService.unfollow(user.getId(), entityType, entityId); 63 | 64 | return CommunityUtil.getJSONString(0, "已取消关注!"); 65 | } 66 | 67 | @RequestMapping(path = "/followees/{userId}", method = RequestMethod.GET) 68 | public String getFollowees(@PathVariable("userId") int userId, Page page, Model model) { 69 | User user = userService.findUserById(userId); 70 | if (user == null) { 71 | throw new RuntimeException("该用户不存在!"); 72 | } 73 | model.addAttribute("user", user); 74 | 75 | page.setLimit(5); 76 | page.setPath("/followees/" + userId); 77 | page.setRows((int) followService.findFolloweeCount(userId, ENTITY_TYPE_USER)); 78 | 79 | List> userList = followService.findFollowees(userId, page.getOffset(), page.getLimit()); 80 | if (userList != null) { 81 | for (Map map : userList) { 82 | User u = (User) map.get("user"); 83 | map.put("hasFollowed", hasFollowed(u.getId())); 84 | } 85 | } 86 | model.addAttribute("users", userList); 87 | 88 | return "/site/followee"; 89 | } 90 | 91 | @RequestMapping(path = "/followers/{userId}", method = RequestMethod.GET) 92 | public String getFollowers(@PathVariable("userId") int userId, Page page, Model model) { 93 | User user = userService.findUserById(userId); 94 | if (user == null) { 95 | throw new RuntimeException("该用户不存在!"); 96 | } 97 | model.addAttribute("user", user); 98 | 99 | page.setLimit(5); 100 | page.setPath("/followers/" + userId); 101 | page.setRows((int) followService.findFollowerCount(ENTITY_TYPE_USER, userId)); 102 | 103 | List> userList = followService.findFollowers(userId, page.getOffset(), page.getLimit()); 104 | if (userList != null) { 105 | for (Map map : userList) { 106 | User u = (User) map.get("user"); 107 | //map对象里有hasFollowed、user、followTime 108 | map.put("hasFollowed", hasFollowed(u.getId())); 109 | } 110 | } 111 | model.addAttribute("users", userList); 112 | 113 | return "/site/follower"; 114 | } 115 | 116 | private boolean hasFollowed(int userId) { 117 | if (hostHolder.getUser() == null) { 118 | return false; 119 | } 120 | 121 | return followService.hasFollowed(hostHolder.getUser().getId(), ENTITY_TYPE_USER, userId); 122 | } 123 | 124 | } 125 | -------------------------------------------------------------------------------- /src/main/java/com/community/controller/HomeController.java: -------------------------------------------------------------------------------- 1 | package com.community.controller; 2 | 3 | import com.community.entity.DiscussPost; 4 | import com.community.entity.Page; 5 | import com.community.entity.User; 6 | import com.community.service.DiscussPostService; 7 | import com.community.service.LikeService; 8 | import com.community.service.UserService; 9 | import com.community.util.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 | import org.springframework.web.bind.annotation.RequestParam; 16 | 17 | import java.util.ArrayList; 18 | import java.util.HashMap; 19 | import java.util.List; 20 | import java.util.Map; 21 | 22 | @Controller 23 | //访问路径的时候前面还要加上community 24 | public class HomeController implements CommunityConstant { 25 | 26 | @Autowired 27 | private DiscussPostService discussPostService; 28 | 29 | @Autowired 30 | private UserService userService; 31 | 32 | @Autowired 33 | private LikeService likeService; 34 | 35 | @RequestMapping(path = "/index", method = RequestMethod.GET) 36 | //查询全部帖子,并显示在首页上 37 | public String getIndexPage(Model model, Page page, 38 | @RequestParam(name = "orderMode", defaultValue = "0") int orderMode) { 39 | // 方法调用前,SpringMVC会自动实例化Model和Page,并将Page注入Model. 40 | // 所以,在thymeleaf中可以直接访问Page对象中的数据. 41 | //最新、最热贴都是置顶具有优先级 42 | page.setRows(discussPostService.findDiscussPostRows(0)); 43 | page.setPath("/index?orderMode="+orderMode); 44 | 45 | List list = discussPostService.findDiscussPosts(0, page.getOffset(), page.getLimit(),orderMode); 46 | List> discussPosts = new ArrayList<>(); 47 | if (list != null) { 48 | for (DiscussPost post : list) { 49 | Map map = new HashMap<>(); 50 | map.put("post", post); 51 | User user = userService.findUserById(post.getUserId()); 52 | map.put("user", user); 53 | 54 | long likeCount = likeService.findEntityLikeCount(ENTITY_TYPE_POST, post.getId()); 55 | map.put("likeCount", likeCount); 56 | 57 | discussPosts.add(map); 58 | } 59 | } 60 | model.addAttribute("discussPosts", discussPosts); 61 | model.addAttribute("orderMode",orderMode); 62 | return "/index"; 63 | } 64 | 65 | @RequestMapping(path = "/error", method = RequestMethod.GET) 66 | public String getErrorPage() { 67 | return "/error/500"; 68 | } 69 | 70 | @RequestMapping(path = "/denied", method = RequestMethod.GET) 71 | public String getDeniedPage() { 72 | return "/error/404"; 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/com/community/controller/LikeController.java: -------------------------------------------------------------------------------- 1 | package com.community.controller; 2 | 3 | import com.community.entity.User; 4 | import com.community.util.CommunityUtil; 5 | import com.community.util.HostHolder; 6 | import com.community.entity.Event; 7 | import com.community.event.EventProducer; 8 | import com.community.service.LikeService; 9 | import com.community.util.CommunityConstant; 10 | import com.community.util.RedisKeyUtil; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.data.redis.core.RedisTemplate; 13 | import org.springframework.stereotype.Controller; 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.HashMap; 19 | import java.util.Map; 20 | 21 | @Controller 22 | public class LikeController implements CommunityConstant { 23 | 24 | @Autowired 25 | private LikeService likeService; 26 | 27 | @Autowired 28 | private HostHolder hostHolder; 29 | 30 | @Autowired 31 | private EventProducer eventProducer; 32 | 33 | @Autowired 34 | private RedisTemplate redisTemplate; 35 | 36 | @RequestMapping(path = "/like", method = RequestMethod.POST) 37 | @ResponseBody 38 | //entityType这些数据是由js传递过来的 39 | public String like(int entityType, int entityId,int entityUserId,int postId) { 40 | User user = hostHolder.getUser(); 41 | 42 | // 点赞 43 | likeService.like(user.getId(), entityType, entityId,entityUserId); 44 | 45 | // 数量 46 | long likeCount = likeService.findEntityLikeCount(entityType, entityId); 47 | // 状态 48 | int likeStatus = likeService.findEntityLikeStatus(user.getId(), entityType, entityId); 49 | // 返回的结果 50 | Map map = new HashMap<>(); 51 | map.put("likeCount", likeCount); 52 | map.put("likeStatus", likeStatus); 53 | 54 | // 触发点赞事件 55 | if (likeStatus == 1) { 56 | Event event = new Event() 57 | .setTopic(TOPIC_LIKE) 58 | .setUserId(hostHolder.getUser().getId()) 59 | .setEntityType(entityType) 60 | .setEntityId(entityId) 61 | .setEntityUserId(entityUserId) 62 | .setData("postId", postId); 63 | eventProducer.fireEvent(event); 64 | } 65 | 66 | if(entityType == ENTITY_TYPE_POST) { 67 | // 计算帖子分数 68 | String redisKey = RedisKeyUtil.getPostScoreKey(); 69 | redisTemplate.opsForSet().add(redisKey, postId); 70 | } 71 | 72 | //主要用于js中读取数据,data.likeCount 73 | return CommunityUtil.getJSONString(0, null, map); 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/com/community/controller/SearchController.java: -------------------------------------------------------------------------------- 1 | package com.community.controller; 2 | 3 | import com.community.entity.DiscussPost; 4 | import com.community.entity.Page; 5 | import com.community.service.ElasticsearchService; 6 | import com.community.service.LikeService; 7 | import com.community.service.UserService; 8 | import com.community.util.CommunityConstant; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.stereotype.Controller; 11 | import org.springframework.ui.Model; 12 | import org.springframework.web.bind.annotation.RequestMapping; 13 | import org.springframework.web.bind.annotation.RequestMethod; 14 | 15 | import java.util.ArrayList; 16 | import java.util.HashMap; 17 | import java.util.List; 18 | import java.util.Map; 19 | 20 | @Controller 21 | public class SearchController implements CommunityConstant { 22 | 23 | @Autowired 24 | private ElasticsearchService elasticsearchService; 25 | 26 | @Autowired 27 | private UserService userService; 28 | 29 | @Autowired 30 | private LikeService likeService; 31 | 32 | // search?keyword=xxx 33 | @RequestMapping(path = "/search", method = RequestMethod.GET) 34 | public String search(String keyword, Page page, Model model) { 35 | // 搜索帖子 36 | org.springframework.data.domain.Page searchResult = 37 | elasticsearchService.searchDiscussPost(keyword, page.getCurrent() - 1, page.getLimit()); 38 | // 聚合数据 39 | List> discussPosts = new ArrayList<>(); 40 | if (searchResult != null) { 41 | for (DiscussPost post : searchResult) { 42 | Map map = new HashMap<>(); 43 | // 帖子 44 | map.put("post", post); 45 | // 作者 46 | map.put("user", userService.findUserById(post.getUserId())); 47 | // 点赞数量 48 | map.put("likeCount", likeService.findEntityLikeCount(ENTITY_TYPE_POST, post.getId())); 49 | 50 | discussPosts.add(map); 51 | } 52 | } 53 | model.addAttribute("discussPosts", discussPosts); 54 | model.addAttribute("keyword", keyword); 55 | 56 | // 分页信息 57 | page.setPath("/search?keyword=" + keyword); 58 | page.setRows(searchResult == null ? 0 : (int) searchResult.getTotalElements()); 59 | 60 | return "/site/search"; 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/com/community/controller/ShareController.java: -------------------------------------------------------------------------------- 1 | package com.community.controller; 2 | 3 | import com.community.util.CommunityUtil; 4 | import com.community.entity.Event; 5 | import com.community.event.EventProducer; 6 | import com.community.util.CommunityConstant; 7 | import org.apache.commons.lang3.StringUtils; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.beans.factory.annotation.Value; 12 | import org.springframework.stereotype.Controller; 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 javax.servlet.http.HttpServletResponse; 19 | import java.io.File; 20 | import java.io.FileInputStream; 21 | import java.io.IOException; 22 | import java.io.OutputStream; 23 | import java.util.HashMap; 24 | import java.util.Map; 25 | 26 | @Controller 27 | public class ShareController implements CommunityConstant { 28 | 29 | private static final Logger logger = LoggerFactory.getLogger(ShareController.class); 30 | 31 | @Autowired 32 | private EventProducer eventProducer; 33 | 34 | @Value("${community.path.domain}") 35 | private String domain; 36 | 37 | @Value("${server.servlet.context-path}") 38 | private String contextPath; 39 | 40 | @Value("${wk.image.storage}") 41 | private String wkImageStorage; 42 | 43 | @Value("${qiniu.bucket.share.url}") 44 | private String shareBucketUrl; 45 | 46 | @RequestMapping(path = "/share", method = RequestMethod.GET) 47 | @ResponseBody 48 | //什么注解都不加,并且get方式,htmlUrl用?htmlUrl=*****,服务器可以获得该值 49 | public String share(String htmlUrl) { 50 | // 文件名 51 | String fileName = CommunityUtil.generateUUID(); 52 | 53 | // 异步生成长图 54 | Event event = new Event() 55 | .setTopic(TOPIC_SHARE) 56 | .setData("htmlUrl", htmlUrl) 57 | .setData("fileName", fileName) 58 | .setData("suffix", ".png"); 59 | eventProducer.fireEvent(event); 60 | 61 | // 返回访问路径 62 | Map map = new HashMap<>(); 63 | // map.put("shareUrl", domain + contextPath + "/share/image/" + fileName); 64 | map.put("shareUrl", shareBucketUrl + "/" + fileName); 65 | 66 | return CommunityUtil.getJSONString(0, null, map); 67 | } 68 | 69 | //废弃 70 | // 获取长图 71 | @RequestMapping(path = "/share/image/{fileName}", method = RequestMethod.GET) 72 | public void getShareImage(@PathVariable("fileName") String fileName, HttpServletResponse response) { 73 | if (StringUtils.isBlank(fileName)) { 74 | throw new IllegalArgumentException("文件名不能为空!"); 75 | } 76 | 77 | response.setContentType("image/png"); 78 | File file = new File(wkImageStorage + "/" + fileName + ".png"); 79 | try { 80 | OutputStream os = response.getOutputStream(); 81 | FileInputStream fis = new FileInputStream(file); 82 | byte[] buffer = new byte[1024]; 83 | int b = 0; 84 | while ((b = fis.read(buffer)) != -1) { 85 | os.write(buffer, 0, b); 86 | } 87 | } catch (IOException e) { 88 | logger.error("获取长图失败: " + e.getMessage()); 89 | } 90 | } 91 | 92 | } 93 | -------------------------------------------------------------------------------- /src/main/java/com/community/controller/advice/ExceptionAdvice.java: -------------------------------------------------------------------------------- 1 | package com.community.controller.advice; 2 | 3 | import com.community.util.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 | //这个组件去扫描所有带有controller注解的那些bean。这是统一处理异常的类 17 | public class ExceptionAdvice { 18 | 19 | private static final Logger logger = LoggerFactory.getLogger(ExceptionAdvice.class); 20 | 21 | @ExceptionHandler({Exception.class}) 22 | public void handleException(Exception e, HttpServletRequest request, HttpServletResponse response) throws IOException { 23 | logger.error("服务器发生异常: " + e.getMessage()); 24 | for (StackTraceElement element : e.getStackTrace()) { 25 | logger.error(element.toString()); 26 | } 27 | 28 | String xRequestedWith = request.getHeader("x-requested-with"); 29 | //"XMLHttpRequest".equals(xRequestedWith)说明是一个异步请求。代码有误就会报服务器异常 30 | if ("XMLHttpRequest".equals(xRequestedWith)) { 31 | response.setContentType("application/plain;charset=utf-8"); 32 | PrintWriter writer = response.getWriter(); 33 | writer.write(CommunityUtil.getJSONString(1, "服务器异常!")); 34 | } else { 35 | response.sendRedirect(request.getContextPath() + "/error"); 36 | } 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/community/controller/interceptor/AlphaInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.community.controller.interceptor; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.stereotype.Component; 6 | import org.springframework.web.servlet.HandlerInterceptor; 7 | import org.springframework.web.servlet.ModelAndView; 8 | 9 | import javax.servlet.http.HttpServletRequest; 10 | import javax.servlet.http.HttpServletResponse; 11 | 12 | @Component 13 | public class AlphaInterceptor implements HandlerInterceptor { 14 | 15 | private static final Logger logger = LoggerFactory.getLogger(AlphaInterceptor.class); 16 | 17 | // 在Controller之前执行 18 | @Override 19 | public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { 20 | logger.debug("preHandle: " + handler.toString()); 21 | return true; 22 | } 23 | 24 | // 在Controller之后执行 25 | @Override 26 | public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { 27 | logger.debug("postHandle: " + handler.toString()); 28 | } 29 | 30 | // 在TemplateEngine之后执行 31 | @Override 32 | public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { 33 | logger.debug("afterCompletion: " + handler.toString()); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/community/controller/interceptor/DataInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.community.controller.interceptor; 2 | 3 | import com.community.entity.User; 4 | import com.community.service.DataService; 5 | import com.community.util.HostHolder; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.stereotype.Component; 8 | import org.springframework.web.servlet.HandlerInterceptor; 9 | 10 | import javax.servlet.http.HttpServletRequest; 11 | import javax.servlet.http.HttpServletResponse; 12 | 13 | @Component 14 | public class DataInterceptor implements HandlerInterceptor { 15 | 16 | @Autowired 17 | private DataService dataService; 18 | 19 | @Autowired 20 | private HostHolder hostHolder; 21 | 22 | //在Controller之前执行 23 | @Override 24 | public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { 25 | // 统计UV 26 | String ip = request.getRemoteHost(); 27 | dataService.recordUV(ip); 28 | 29 | // 统计DAU 30 | User user = hostHolder.getUser(); 31 | if (user != null) { 32 | dataService.recordDAU(user.getId()); 33 | } 34 | 35 | return true; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/community/controller/interceptor/LoginRequiredInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.community.controller.interceptor; 2 | 3 | import com.community.util.HostHolder; 4 | import com.community.annotation.LoginRequired; 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 | //对需要登录才能访问的资源进行拦截,已弃用,改为Spring Security管理 15 | @Component 16 | public class LoginRequiredInterceptor implements HandlerInterceptor { 17 | 18 | @Autowired 19 | private HostHolder hostHolder; 20 | 21 | @Override 22 | public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { 23 | //instanceof代表拦截器只对方法进行拦截,对静态资源不进行拦截 24 | if (handler instanceof HandlerMethod) { 25 | HandlerMethod handlerMethod = (HandlerMethod) handler; 26 | Method method = handlerMethod.getMethod(); 27 | LoginRequired loginRequired = method.getAnnotation(LoginRequired.class); 28 | if (loginRequired != null && hostHolder.getUser() == null) { 29 | response.sendRedirect(request.getContextPath() + "/login"); 30 | return false; 31 | } 32 | } 33 | return true; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/community/controller/interceptor/LoginTicketInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.community.controller.interceptor; 2 | 3 | import com.community.entity.LoginTicket; 4 | import com.community.entity.User; 5 | import com.community.service.UserService; 6 | import com.community.util.CookieUtil; 7 | import com.community.util.HostHolder; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 10 | import org.springframework.security.core.Authentication; 11 | import org.springframework.security.core.context.SecurityContextHolder; 12 | import org.springframework.security.core.context.SecurityContextImpl; 13 | import org.springframework.stereotype.Component; 14 | import org.springframework.web.servlet.HandlerInterceptor; 15 | import org.springframework.web.servlet.ModelAndView; 16 | 17 | import javax.servlet.http.HttpServletRequest; 18 | import javax.servlet.http.HttpServletResponse; 19 | import java.util.Date; 20 | 21 | @Component 22 | public class LoginTicketInterceptor implements HandlerInterceptor { 23 | 24 | @Autowired 25 | private UserService userService; 26 | 27 | @Autowired 28 | private HostHolder hostHolder; 29 | 30 | //在Controller之前执行 31 | @Override 32 | public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { 33 | // 从cookie中获取凭证 34 | String ticket = CookieUtil.getValue(request, "ticket"); 35 | 36 | if (ticket != null) { 37 | // 查询凭证 38 | LoginTicket loginTicket = userService.findLoginTicket(ticket); 39 | // 检查凭证是否有效 40 | if (loginTicket != null && loginTicket.getStatus() == 0 && loginTicket.getExpired().after(new Date())) { 41 | // 根据凭证查询用户 42 | User user = userService.findUserById(loginTicket.getUserId()); 43 | // 在本次请求中持有用户,访问一个网页就是一个请求,如/login,/register 44 | hostHolder.setUser(user); 45 | // 查询用户的权限,并存入SecurityContext,以便于Security进行授权。 46 | Authentication authentication = new UsernamePasswordAuthenticationToken( 47 | user, user.getPassword(), userService.getAuthorities(user.getId())); 48 | SecurityContextHolder.setContext(new SecurityContextImpl(authentication)); 49 | } 50 | } 51 | 52 | return true; 53 | } 54 | 55 | //在Controller之后执行 56 | @Override 57 | public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { 58 | User user = hostHolder.getUser(); 59 | if (user != null && modelAndView != null) { 60 | modelAndView.addObject("loginUser", user); 61 | } 62 | } 63 | 64 | //在TemplateEngine之后执行 65 | @Override 66 | public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { 67 | //在线程中把数据清掉,节约空间 68 | hostHolder.clear(); 69 | SecurityContextHolder.clearContext(); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/com/community/controller/interceptor/MessageInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.community.controller.interceptor; 2 | 3 | import com.community.entity.User; 4 | import com.community.service.MessageService; 5 | import com.community.util.HostHolder; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.stereotype.Component; 8 | import org.springframework.web.servlet.HandlerInterceptor; 9 | import org.springframework.web.servlet.ModelAndView; 10 | 11 | import javax.servlet.http.HttpServletRequest; 12 | import javax.servlet.http.HttpServletResponse; 13 | 14 | @Component 15 | public class MessageInterceptor implements HandlerInterceptor { 16 | 17 | @Autowired 18 | private HostHolder hostHolder; 19 | 20 | @Autowired 21 | private MessageService messageService; 22 | 23 | //在Controller之后执行,写完拦截器后要在WebMvcConfig中配置 24 | @Override 25 | public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { 26 | User user = hostHolder.getUser(); 27 | if (user != null && modelAndView != null) { 28 | int letterUnreadCount = messageService.findLetterUnreadCount(user.getId(), null); 29 | int noticeUnreadCount = messageService.findNoticeUnreadCount(user.getId(), null); 30 | modelAndView.addObject("allUnreadCount", letterUnreadCount + noticeUnreadCount); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/community/dao/AlphaDao.java: -------------------------------------------------------------------------------- 1 | package com.community.dao; 2 | 3 | public interface AlphaDao { 4 | 5 | String select(); 6 | 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/community/dao/AlphaDaoHibernateImpl.java: -------------------------------------------------------------------------------- 1 | package com.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/java/com/community/dao/AlphaDaoMyBatisImpl.java: -------------------------------------------------------------------------------- 1 | package com.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/community/dao/CommentMapper.java: -------------------------------------------------------------------------------- 1 | package com.community.dao; 2 | 3 | import com.community.entity.Comment; 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 CommentMapper { 11 | 12 | List selectCommentsByEntity(@Param("entityType")int entityType, @Param("entityId")int entityId, @Param("offset")int offset, @Param("limit")int limit); 13 | 14 | int selectCountByEntity(@Param("entityType")int entityType, @Param("entityId")int entityId); 15 | 16 | int insertComment(Comment comment); 17 | 18 | Comment selectCommentById(@Param("id")int id); 19 | 20 | int selectCommentCountById(@Param("id")int id); 21 | 22 | List selectCommentsByUserId(@Param("id")int id,@Param("offset")int offset,@Param("limit")int limit); 23 | 24 | int updateStatus(@Param("entityId")int entityId, @Param("status")int status); 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/community/dao/DiscussPostMapper.java: -------------------------------------------------------------------------------- 1 | package com.community.dao; 2 | 3 | import com.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 | //orderMode排序模式,默认为0。1是按帖子热度排序 13 | List selectDiscussPosts( 14 | @Param("userId") int userId, @Param("offset")int offset, @Param("limit")int limit, @Param("orderMode")int orderMode); 15 | 16 | // @Param注解用于给参数取别名,只有一个变量可以不加,要是有两个一定要加,所以一般都加上比较保险 17 | // sql在里使用,则所有参数必须加别名. 18 | int selectDiscussPostRows(@Param("userId") int userId); 19 | 20 | int insertDiscussPost(DiscussPost discussPost); 21 | 22 | DiscussPost selectDiscussPostById(@Param("id")int id); 23 | 24 | int updateCommentCount(@Param("id")int id, @Param("commentCount")int commentCount); 25 | 26 | int updateType(@Param("id")int id,@Param("type") int type); 27 | 28 | int updateStatus(@Param("id")int id, @Param("status")int status); 29 | 30 | int updateScore(@Param("id")int id, @Param("score")double score); 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/community/dao/LoginTicketMapper.java: -------------------------------------------------------------------------------- 1 | package com.community.dao; 2 | 3 | import com.community.entity.LoginTicket; 4 | import org.apache.ibatis.annotations.*; 5 | 6 | @Mapper 7 | //弃用,因为重构代码将用户登录信息存到redis中,为了提高效率 8 | public interface LoginTicketMapper { 9 | 10 | @Insert({ 11 | "insert into login_ticket(user_id,ticket,status,expired) ", 12 | "values(#{userId},#{ticket},#{status},#{expired})" 13 | }) 14 | @Options(useGeneratedKeys = true, keyProperty = "id") 15 | int insertLoginTicket(LoginTicket loginTicket); 16 | 17 | @Select({ 18 | "select id,user_id,ticket,status,expired ", 19 | "from login_ticket where ticket=#{ticket}" 20 | }) 21 | LoginTicket selectByTicket(@Param("ticket")String ticket); 22 | 23 | @Update({ 24 | "" 30 | }) 31 | int updateStatus(@Param("ticket")String ticket, @Param("status")int status); 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/community/dao/MessageMapper.java: -------------------------------------------------------------------------------- 1 | package com.community.dao; 2 | 3 | import com.community.entity.Message; 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 MessageMapper { 11 | 12 | // 查询当前用户的会话列表,针对每个会话只返回一条最新的私信. 13 | List selectConversations(@Param("userId")int userId, @Param("offset")int offset, @Param("limit")int limit); 14 | 15 | // 查询当前用户的会话数量. 16 | int selectConversationCount(@Param("userId")int userId); 17 | 18 | // 查询某个会话所包含的私信列表. 19 | List selectLetters(@Param("conversationId")String conversationId, @Param("offset")int offset,@Param("limit") int limit); 20 | 21 | // 查询某个会话所包含的私信数量. 22 | int selectLetterCount(@Param("conversationId")String conversationId); 23 | 24 | // 查询未读私信的数量 25 | int selectLetterUnreadCount(@Param("userId")int userId, @Param("conversationId")String conversationId); 26 | 27 | // 新增消息 28 | int insertMessage(Message message); 29 | 30 | // 修改消息的状态 31 | int updateStatus(@Param("ids")List ids,@Param("status") int status); 32 | 33 | // 查询某个主题下最新的通知 34 | Message selectLatestNotice(@Param("userId")int userId, @Param("topic")String topic); 35 | 36 | // 查询某个主题所包含的通知数量 37 | int selectNoticeCount(@Param("userId")int userId, @Param("topic")String topic); 38 | 39 | // 查询未读的通知的数量 40 | int selectNoticeUnreadCount(@Param("userId")int userId,@Param("topic") String topic); 41 | 42 | // 查询某个主题所包含的通知列表 43 | List selectNotices(@Param("userId")int userId, @Param("topic")String topic,@Param("offset") int offset, @Param("limit")int limit); 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/community/dao/UserMapper.java: -------------------------------------------------------------------------------- 1 | package com.community.dao; 2 | 3 | import com.community.entity.User; 4 | import org.apache.ibatis.annotations.Mapper; 5 | import org.apache.ibatis.annotations.Param; 6 | 7 | @Mapper 8 | public interface UserMapper { 9 | //一般都加上@Param比较保险 10 | 11 | User selectById(@Param("id")int id); 12 | 13 | User selectByName(@Param("username")String username); 14 | 15 | User selectByEmail(@Param("email")String email); 16 | 17 | int insertUser(User user); 18 | 19 | int updateStatus(@Param("id")int id, @Param("status")int status); 20 | 21 | int updateHeader(@Param("id")int id, @Param("headerUrl")String headerUrl); 22 | 23 | int updatePassword(@Param("id")int id,@Param("password") String password); 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/community/dao/elasticsearch/DiscussPostRepository.java: -------------------------------------------------------------------------------- 1 | package com.community.dao.elasticsearch; 2 | 3 | import com.community.entity.DiscussPost; 4 | import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; 5 | import org.springframework.stereotype.Repository; 6 | 7 | @Repository 8 | public interface DiscussPostRepository extends ElasticsearchRepository { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/community/entity/Comment.java: -------------------------------------------------------------------------------- 1 | package com.community.entity; 2 | 3 | import java.util.Date; 4 | 5 | public class Comment { 6 | 7 | private int id; 8 | private int userId; 9 | private int entityType; 10 | private int entityId; 11 | private int targetId; 12 | private String content; 13 | private int status; 14 | private Date createTime; 15 | 16 | public int getId() { 17 | return id; 18 | } 19 | 20 | public void setId(int id) { 21 | this.id = id; 22 | } 23 | 24 | public int getUserId() { 25 | return userId; 26 | } 27 | 28 | public void setUserId(int userId) { 29 | this.userId = userId; 30 | } 31 | 32 | public int getEntityType() { 33 | return entityType; 34 | } 35 | 36 | public void setEntityType(int entityType) { 37 | this.entityType = entityType; 38 | } 39 | 40 | public int getEntityId() { 41 | return entityId; 42 | } 43 | 44 | public void setEntityId(int entityId) { 45 | this.entityId = entityId; 46 | } 47 | 48 | public int getTargetId() { 49 | return targetId; 50 | } 51 | 52 | public void setTargetId(int targetId) { 53 | this.targetId = targetId; 54 | } 55 | 56 | public String getContent() { 57 | return content; 58 | } 59 | 60 | public void setContent(String content) { 61 | this.content = content; 62 | } 63 | 64 | public int getStatus() { 65 | return status; 66 | } 67 | 68 | public void setStatus(int status) { 69 | this.status = status; 70 | } 71 | 72 | public Date getCreateTime() { 73 | return createTime; 74 | } 75 | 76 | public void setCreateTime(Date createTime) { 77 | this.createTime = createTime; 78 | } 79 | 80 | @Override 81 | public String toString() { 82 | return "Comment{" + 83 | "id=" + id + 84 | ", userId=" + userId + 85 | ", entityType=" + entityType + 86 | ", entityId=" + entityId + 87 | ", targetId=" + targetId + 88 | ", content='" + content + '\'' + 89 | ", status=" + status + 90 | ", createTime=" + createTime + 91 | '}'; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/main/java/com/community/entity/DiscussPost.java: -------------------------------------------------------------------------------- 1 | package com.community.entity; 2 | 3 | import org.springframework.data.annotation.Id; 4 | import org.springframework.data.elasticsearch.annotations.Document; 5 | import org.springframework.data.elasticsearch.annotations.Field; 6 | import org.springframework.data.elasticsearch.annotations.FieldType; 7 | 8 | import java.util.Date; 9 | 10 | //indexName 索引名字 shards 分片 replicas 副本 11 | @Document(indexName = "discusspost", type = "_doc", shards = 6, replicas = 3) 12 | public class DiscussPost { 13 | 14 | @Id 15 | private int id; 16 | 17 | @Field(type = FieldType.Integer) 18 | private int userId; 19 | 20 | // 搜索帖子主要依据title、content 21 | //存储的时候用analyzer分词器,搜索的时候用searchAnalyzer分词器 22 | @Field(type = FieldType.Text, analyzer = "ik_max_word", searchAnalyzer = "ik_smart") 23 | private String title; 24 | 25 | @Field(type = FieldType.Text, analyzer = "ik_max_word", searchAnalyzer = "ik_smart") 26 | private String content; 27 | 28 | @Field(type = FieldType.Integer) //0 普通 1 置顶 29 | private int type; 30 | 31 | @Field(type = FieldType.Integer) //0 正常 1 精华 2 拉黑 32 | private int status; 33 | 34 | @Field(type = FieldType.Date) 35 | private Date createTime; 36 | 37 | @Field(type = FieldType.Integer) 38 | private int commentCount; 39 | 40 | @Field(type = FieldType.Double) 41 | private double score; 42 | 43 | public int getId() { 44 | return id; 45 | } 46 | 47 | public void setId(int id) { 48 | this.id = id; 49 | } 50 | 51 | public int getUserId() { 52 | return userId; 53 | } 54 | 55 | public void setUserId(int userId) { 56 | this.userId = userId; 57 | } 58 | 59 | public String getTitle() { 60 | return title; 61 | } 62 | 63 | public void setTitle(String title) { 64 | this.title = title; 65 | } 66 | 67 | public String getContent() { 68 | return content; 69 | } 70 | 71 | public void setContent(String content) { 72 | this.content = content; 73 | } 74 | 75 | public int getType() { 76 | return type; 77 | } 78 | 79 | public void setType(int type) { 80 | this.type = type; 81 | } 82 | 83 | public int getStatus() { 84 | return status; 85 | } 86 | 87 | public void setStatus(int status) { 88 | this.status = status; 89 | } 90 | 91 | public Date getCreateTime() { 92 | return createTime; 93 | } 94 | 95 | public void setCreateTime(Date createTime) { 96 | this.createTime = createTime; 97 | } 98 | 99 | public int getCommentCount() { 100 | return commentCount; 101 | } 102 | 103 | public void setCommentCount(int commentCount) { 104 | this.commentCount = commentCount; 105 | } 106 | 107 | public double getScore() { 108 | return score; 109 | } 110 | 111 | public void setScore(double score) { 112 | this.score = score; 113 | } 114 | 115 | @Override 116 | public String toString() { 117 | return "DiscussPost{" + 118 | "id=" + id + 119 | ", userId=" + userId + 120 | ", title='" + title + '\'' + 121 | ", content='" + content + '\'' + 122 | ", type=" + type + 123 | ", status=" + status + 124 | ", createTime=" + createTime + 125 | ", commentCount=" + commentCount + 126 | ", score=" + score + 127 | '}'; 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/main/java/com/community/entity/Event.java: -------------------------------------------------------------------------------- 1 | package com.community.entity; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | public class Event { 7 | 8 | //事件主题,是评论、点赞还是关注 9 | private String topic; 10 | //事件是由谁触发的 userId 11 | private int userId; 12 | //事件实体的类型,是帖子、评论还是用户 entityType 13 | private int entityType; 14 | //事件实体的id entityId 15 | private int entityId; 16 | //系统消息发送的对象,entityUserId 17 | private int entityUserId; 18 | private Map data = new HashMap<>(); 19 | 20 | public String getTopic() { 21 | return topic; 22 | } 23 | 24 | public Event setTopic(String topic) { 25 | this.topic = topic; 26 | return this; 27 | } 28 | 29 | public int getUserId() { 30 | return userId; 31 | } 32 | 33 | public Event setUserId(int userId) { 34 | this.userId = userId; 35 | return this; 36 | } 37 | 38 | public int getEntityType() { 39 | return entityType; 40 | } 41 | 42 | public Event setEntityType(int entityType) { 43 | this.entityType = entityType; 44 | return this; 45 | } 46 | 47 | public int getEntityId() { 48 | return entityId; 49 | } 50 | 51 | public Event setEntityId(int entityId) { 52 | this.entityId = entityId; 53 | return this; 54 | } 55 | 56 | public int getEntityUserId() { 57 | return entityUserId; 58 | } 59 | 60 | public Event setEntityUserId(int entityUserId) { 61 | this.entityUserId = entityUserId; 62 | return this; 63 | } 64 | 65 | public Map getData() { 66 | return data; 67 | } 68 | 69 | public Event setData(String key, Object value) { 70 | this.data.put(key, value); 71 | return this; 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/com/community/entity/LoginTicket.java: -------------------------------------------------------------------------------- 1 | package com.community.entity; 2 | 3 | import java.util.Date; 4 | 5 | public class LoginTicket { 6 | private int id; 7 | private int userId; 8 | private String ticket; 9 | private int status; 10 | private Date expired; 11 | 12 | public int getId() { 13 | return id; 14 | } 15 | 16 | public void setId(int id) { 17 | this.id = id; 18 | } 19 | 20 | public int getUserId() { 21 | return userId; 22 | } 23 | 24 | public void setUserId(int userId) { 25 | this.userId = userId; 26 | } 27 | 28 | public String getTicket() { 29 | return ticket; 30 | } 31 | 32 | public void setTicket(String ticket) { 33 | this.ticket = ticket; 34 | } 35 | 36 | public int getStatus() { 37 | return status; 38 | } 39 | 40 | public void setStatus(int status) { 41 | this.status = status; 42 | } 43 | 44 | public Date getExpired() { 45 | return expired; 46 | } 47 | 48 | public void setExpired(Date expired) { 49 | this.expired = expired; 50 | } 51 | 52 | @Override 53 | public String toString() { 54 | return "LoginTicket{" + 55 | "id=" + id + 56 | ", userId=" + userId + 57 | ", ticket='" + ticket + '\'' + 58 | ", status=" + status + 59 | ", expired=" + expired + 60 | '}'; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/com/community/entity/Message.java: -------------------------------------------------------------------------------- 1 | package com.community.entity; 2 | 3 | import java.util.Date; 4 | 5 | public class Message { 6 | 7 | private int id; 8 | private int fromId; 9 | private int toId; 10 | private String conversationId; 11 | private String content; 12 | private int status; 13 | private Date createTime; 14 | 15 | public int getId() { 16 | return id; 17 | } 18 | 19 | public void setId(int id) { 20 | this.id = id; 21 | } 22 | 23 | public int getFromId() { 24 | return fromId; 25 | } 26 | 27 | public void setFromId(int fromId) { 28 | this.fromId = fromId; 29 | } 30 | 31 | public int getToId() { 32 | return toId; 33 | } 34 | 35 | public void setToId(int toId) { 36 | this.toId = toId; 37 | } 38 | 39 | public String getConversationId() { 40 | return conversationId; 41 | } 42 | 43 | public void setConversationId(String conversationId) { 44 | this.conversationId = conversationId; 45 | } 46 | 47 | public String getContent() { 48 | return content; 49 | } 50 | 51 | public void setContent(String content) { 52 | this.content = content; 53 | } 54 | 55 | public int getStatus() { 56 | return status; 57 | } 58 | 59 | public void setStatus(int status) { 60 | this.status = status; 61 | } 62 | 63 | public Date getCreateTime() { 64 | return createTime; 65 | } 66 | 67 | public void setCreateTime(Date createTime) { 68 | this.createTime = createTime; 69 | } 70 | 71 | @Override 72 | public String toString() { 73 | return "Message{" + 74 | "id=" + id + 75 | ", fromId=" + fromId + 76 | ", toId=" + toId + 77 | ", conversationId='" + conversationId + '\'' + 78 | ", content='" + content + '\'' + 79 | ", status=" + status + 80 | ", createTime=" + createTime + 81 | '}'; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/com/community/entity/Page.java: -------------------------------------------------------------------------------- 1 | package com.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/java/com/community/entity/User.java: -------------------------------------------------------------------------------- 1 | package com.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 | //0-普通用户 1-版主 2-管理员 13 | private int type; 14 | private int status; 15 | private String activationCode; 16 | private String headerUrl; 17 | private Date createTime; 18 | 19 | public int getId() { 20 | return id; 21 | } 22 | 23 | public void setId(int id) { 24 | this.id = id; 25 | } 26 | 27 | public String getUsername() { 28 | return username; 29 | } 30 | 31 | public void setUsername(String username) { 32 | this.username = username; 33 | } 34 | 35 | public String getPassword() { 36 | return password; 37 | } 38 | 39 | public void setPassword(String password) { 40 | this.password = password; 41 | } 42 | 43 | public String getSalt() { 44 | return salt; 45 | } 46 | 47 | public void setSalt(String salt) { 48 | this.salt = salt; 49 | } 50 | 51 | public String getEmail() { 52 | return email; 53 | } 54 | 55 | public void setEmail(String email) { 56 | this.email = email; 57 | } 58 | 59 | public int getType() { 60 | return type; 61 | } 62 | 63 | public void setType(int type) { 64 | this.type = type; 65 | } 66 | 67 | public int getStatus() { 68 | return status; 69 | } 70 | 71 | public void setStatus(int status) { 72 | this.status = status; 73 | } 74 | 75 | public String getActivationCode() { 76 | return activationCode; 77 | } 78 | 79 | public void setActivationCode(String activationCode) { 80 | this.activationCode = activationCode; 81 | } 82 | 83 | public String getHeaderUrl() { 84 | return headerUrl; 85 | } 86 | 87 | public void setHeaderUrl(String headerUrl) { 88 | this.headerUrl = headerUrl; 89 | } 90 | 91 | public Date getCreateTime() { 92 | return createTime; 93 | } 94 | 95 | public void setCreateTime(Date createTime) { 96 | this.createTime = createTime; 97 | } 98 | 99 | @Override 100 | public String toString() { 101 | return "User{" + 102 | "id=" + id + 103 | ", username='" + username + '\'' + 104 | ", password='" + password + '\'' + 105 | ", salt='" + salt + '\'' + 106 | ", email='" + email + '\'' + 107 | ", type=" + type + 108 | ", status=" + status + 109 | ", activationCode='" + activationCode + '\'' + 110 | ", headerUrl='" + headerUrl + '\'' + 111 | ", createTime=" + createTime + 112 | '}'; 113 | } 114 | 115 | } 116 | -------------------------------------------------------------------------------- /src/main/java/com/community/event/EventProducer.java: -------------------------------------------------------------------------------- 1 | package com.community.event; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import com.community.entity.Event; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.kafka.core.KafkaTemplate; 7 | import org.springframework.stereotype.Component; 8 | 9 | @Component 10 | public class EventProducer { 11 | 12 | @Autowired 13 | private KafkaTemplate kafkaTemplate; 14 | 15 | // 处理事件 16 | public void fireEvent(Event event) { 17 | // 将事件发布到指定的主题,消息的内容是JSON字符串 18 | kafkaTemplate.send(event.getTopic(), JSONObject.toJSONString(event)); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/community/quartz/AlphaJob.java: -------------------------------------------------------------------------------- 1 | package com.community.quartz; 2 | 3 | import org.quartz.Job; 4 | import org.quartz.JobExecutionContext; 5 | import org.quartz.JobExecutionException; 6 | 7 | public class AlphaJob implements Job { 8 | @Override 9 | public void execute(JobExecutionContext context) throws JobExecutionException { 10 | System.out.println(Thread.currentThread().getName() + ": execute a quartz job."); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/community/quartz/PostScoreRefreshJob.java: -------------------------------------------------------------------------------- 1 | package com.community.quartz; 2 | 3 | import com.community.entity.DiscussPost; 4 | import com.community.service.ElasticsearchService; 5 | import com.community.service.DiscussPostService; 6 | import com.community.service.LikeService; 7 | import com.community.util.CommunityConstant; 8 | import com.community.util.RedisKeyUtil; 9 | import org.quartz.Job; 10 | import org.quartz.JobExecutionContext; 11 | import org.quartz.JobExecutionException; 12 | import org.slf4j.Logger; 13 | import org.slf4j.LoggerFactory; 14 | import org.springframework.beans.factory.annotation.Autowired; 15 | import org.springframework.data.redis.core.BoundSetOperations; 16 | import org.springframework.data.redis.core.RedisTemplate; 17 | 18 | import java.text.ParseException; 19 | import java.text.SimpleDateFormat; 20 | import java.util.Date; 21 | 22 | public class PostScoreRefreshJob implements Job, CommunityConstant { 23 | 24 | private static final Logger logger = LoggerFactory.getLogger(PostScoreRefreshJob.class); 25 | 26 | @Autowired 27 | private RedisTemplate redisTemplate; 28 | 29 | @Autowired 30 | private DiscussPostService discussPostService; 31 | 32 | @Autowired 33 | private LikeService likeService; 34 | 35 | @Autowired 36 | private ElasticsearchService elasticsearchService; 37 | 38 | // Community纪元 39 | private static final Date epoch; 40 | 41 | //初始化Community纪元常量 42 | static { 43 | try { 44 | epoch = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse("2021-01-07 00:00:00"); 45 | } catch (ParseException e) { 46 | throw new RuntimeException("初始化Community纪元失败!", e); 47 | } 48 | } 49 | 50 | @Override 51 | public void execute(JobExecutionContext context) throws JobExecutionException { 52 | String redisKey = RedisKeyUtil.getPostScoreKey(); 53 | BoundSetOperations operations = redisTemplate.boundSetOps(redisKey); 54 | 55 | if (operations.size() == 0) { 56 | logger.info("[任务取消] 没有需要刷新的帖子!"); 57 | return; 58 | } 59 | 60 | logger.info("[任务开始] 正在刷新帖子分数: " + operations.size()); 61 | while (operations.size() > 0) { 62 | this.refresh((Integer) operations.pop()); 63 | } 64 | logger.info("[任务结束] 帖子分数刷新完毕!"); 65 | } 66 | 67 | private void refresh(int postId) { 68 | DiscussPost post = discussPostService.findDiscussPostById(postId); 69 | 70 | if (post == null) { 71 | logger.error("该帖子不存在: id = " + postId); 72 | return; 73 | } 74 | 75 | // 是否精华 76 | boolean wonderful = post.getStatus() == 1; 77 | // 评论数量 78 | int commentCount = post.getCommentCount(); 79 | // 帖子点赞数量(不包含帖子中的评论) 80 | long likeCount = likeService.findEntityLikeCount(ENTITY_TYPE_POST, postId); 81 | 82 | // 计算权重 83 | double w = (wonderful ? 75 : 0) + commentCount * 10 + likeCount * 2; 84 | // 分数 = 帖子权重 + 距离天数 log函数特点:随着x增加,y增加越来越慢 85 | //getTime()得到long型,越新发布的帖子越有可能成为热帖 86 | double score = Math.log10(Math.max(w, 1)) 87 | + (post.getCreateTime().getTime() - epoch.getTime()) / (1000 * 3600 * 24); 88 | // 更新帖子分数 89 | discussPostService.updateScore(postId, score); 90 | // 同步搜索数据 91 | post.setScore(score); 92 | elasticsearchService.saveDiscussPost(post); 93 | } 94 | 95 | } 96 | -------------------------------------------------------------------------------- /src/main/java/com/community/service/AlphaService.java: -------------------------------------------------------------------------------- 1 | package com.community.service; 2 | 3 | import com.community.dao.UserMapper; 4 | import com.community.entity.DiscussPost; 5 | import com.community.entity.User; 6 | import com.community.util.CommunityUtil; 7 | import com.community.dao.AlphaDao; 8 | import com.community.dao.DiscussPostMapper; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.scheduling.annotation.Async; 13 | import org.springframework.stereotype.Service; 14 | import org.springframework.transaction.TransactionDefinition; 15 | import org.springframework.transaction.TransactionStatus; 16 | import org.springframework.transaction.annotation.Isolation; 17 | import org.springframework.transaction.annotation.Propagation; 18 | import org.springframework.transaction.annotation.Transactional; 19 | import org.springframework.transaction.support.TransactionCallback; 20 | import org.springframework.transaction.support.TransactionTemplate; 21 | 22 | import javax.annotation.PostConstruct; 23 | import javax.annotation.PreDestroy; 24 | import java.util.Date; 25 | 26 | @Service 27 | //@Scope("prototype") 28 | public class AlphaService { 29 | private static final Logger logger= LoggerFactory.getLogger(AlphaService.class); 30 | 31 | @Autowired 32 | private AlphaDao alphaDao; 33 | 34 | @Autowired 35 | private UserMapper userMapper; 36 | 37 | @Autowired 38 | private DiscussPostMapper discussPostMapper; 39 | 40 | @Autowired 41 | private TransactionTemplate transactionTemplate; 42 | 43 | public AlphaService() { 44 | // System.out.println("实例化AlphaService"); 45 | } 46 | 47 | @PostConstruct 48 | public void init() { 49 | // System.out.println("初始化AlphaService"); 50 | } 51 | 52 | @PreDestroy 53 | public void destroy() { 54 | // System.out.println("销毁AlphaService"); 55 | } 56 | 57 | public String find() { 58 | return alphaDao.select(); 59 | } 60 | 61 | // REQUIRED: 支持当前事务(外部事务),如果不存在则创建新事务.A调B,A如果有事务就按A;A没有,B新建事务 62 | // REQUIRES_NEW: 创建一个新事务,并且暂停当前事务(外部事务).A调B,B无视A的事务 63 | // NESTED: 如果当前存在事务(外部事务),则嵌套在该事务中执行(独立的提交和回滚),否则就会REQUIRED一样. 64 | //声明式事务:当程序发生异常时,不会插入数据 65 | @Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED) 66 | public Object save1() { 67 | // 新增用户 68 | User user = new User(); 69 | user.setUsername("alpha"); 70 | user.setSalt(CommunityUtil.generateUUID().substring(0, 5)); 71 | user.setPassword(CommunityUtil.md5("123" + user.getSalt())); 72 | user.setEmail("alpha@qq.com"); 73 | user.setHeaderUrl("http://image.nowcoder.com/head/99t.png"); 74 | user.setCreateTime(new Date()); 75 | userMapper.insertUser(user); 76 | 77 | // 新增帖子 78 | DiscussPost post = new DiscussPost(); 79 | post.setUserId(user.getId()); 80 | post.setTitle("Hello"); 81 | post.setContent("新人报道!"); 82 | post.setCreateTime(new Date()); 83 | discussPostMapper.insertDiscussPost(post); 84 | 85 | Integer.valueOf("abc"); 86 | 87 | return "ok"; 88 | } 89 | 90 | //编程式事务,比较复杂,但能抽取出某段代码单独执行事务;一般使用声明式事务 91 | public Object save2() { 92 | transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED); 93 | transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); 94 | 95 | return transactionTemplate.execute(new TransactionCallback() { 96 | @Override 97 | public Object doInTransaction(TransactionStatus status) { 98 | // 新增用户 99 | User user = new User(); 100 | user.setUsername("beta"); 101 | user.setSalt(CommunityUtil.generateUUID().substring(0, 5)); 102 | user.setPassword(CommunityUtil.md5("123" + user.getSalt())); 103 | user.setEmail("beta@qq.com"); 104 | user.setHeaderUrl("http://image.nowcoder.com/head/999t.png"); 105 | user.setCreateTime(new Date()); 106 | userMapper.insertUser(user); 107 | 108 | // 新增帖子 109 | DiscussPost post = new DiscussPost(); 110 | post.setUserId(user.getId()); 111 | post.setTitle("你好"); 112 | post.setContent("我是新人!"); 113 | post.setCreateTime(new Date()); 114 | discussPostMapper.insertDiscussPost(post); 115 | 116 | Integer.valueOf("abc"); 117 | 118 | return "ok"; 119 | } 120 | }); 121 | } 122 | 123 | // 让该方法在多线程环境下,被异步的调用. 124 | @Async 125 | public void execute1() { 126 | logger.debug("execute1"); 127 | } 128 | 129 | //10s后执行,每次间隔1s 130 | //@Scheduled(initialDelay = 10000, fixedRate = 1000) 131 | public void execute2() { 132 | logger.debug("execute2"); 133 | } 134 | 135 | } 136 | -------------------------------------------------------------------------------- /src/main/java/com/community/service/CommentService.java: -------------------------------------------------------------------------------- 1 | package com.community.service; 2 | 3 | import com.community.dao.CommentMapper; 4 | import com.community.entity.Comment; 5 | import com.community.util.CommunityConstant; 6 | import com.community.util.SensitiveFilter; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.stereotype.Service; 9 | import org.springframework.transaction.annotation.Isolation; 10 | import org.springframework.transaction.annotation.Propagation; 11 | import org.springframework.transaction.annotation.Transactional; 12 | import org.springframework.web.util.HtmlUtils; 13 | 14 | import java.util.List; 15 | 16 | @Service 17 | public class CommentService implements CommunityConstant { 18 | 19 | @Autowired 20 | private CommentMapper commentMapper; 21 | 22 | @Autowired 23 | private SensitiveFilter sensitiveFilter; 24 | 25 | @Autowired 26 | private DiscussPostService discussPostService; 27 | 28 | public List findCommentsByEntity(int entityType, int entityId, int offset, int limit) { 29 | return commentMapper.selectCommentsByEntity(entityType, entityId, offset, limit); 30 | } 31 | 32 | public int findCommentCount(int entityType, int entityId) { 33 | return commentMapper.selectCountByEntity(entityType, entityId); 34 | } 35 | 36 | @Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED) 37 | public int addComment(Comment comment) { 38 | if (comment == null) { 39 | throw new IllegalArgumentException("参数不能为空!"); 40 | } 41 | 42 | // 添加评论 43 | comment.setContent(HtmlUtils.htmlEscape(comment.getContent())); 44 | comment.setContent(sensitiveFilter.filter(comment.getContent())); 45 | int rows = commentMapper.insertComment(comment); 46 | 47 | // 更新帖子评论数量,帖子id就是comment.getEntityId 48 | if (comment.getEntityType() == ENTITY_TYPE_POST) { 49 | int count = commentMapper.selectCountByEntity(comment.getEntityType(), comment.getEntityId()); 50 | discussPostService.updateCommentCount(comment.getEntityId(), count); 51 | } 52 | 53 | return rows; 54 | } 55 | 56 | public Comment findCommentById(int id) { 57 | return commentMapper.selectCommentById(id); 58 | } 59 | 60 | public int findCommentCountById(int id) { 61 | return commentMapper.selectCommentCountById(id); 62 | } 63 | 64 | public List findCommentsByUserId(int id,int offset,int limit) { 65 | return commentMapper.selectCommentsByUserId(id,offset,limit); 66 | } 67 | 68 | public int updateStatus(int entityId, int status) { 69 | return commentMapper.updateStatus(entityId,status); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/com/community/service/DataService.java: -------------------------------------------------------------------------------- 1 | package com.community.service; 2 | 3 | import com.community.util.RedisKeyUtil; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.dao.DataAccessException; 6 | import org.springframework.data.redis.connection.RedisConnection; 7 | import org.springframework.data.redis.connection.RedisStringCommands; 8 | import org.springframework.data.redis.core.RedisCallback; 9 | import org.springframework.data.redis.core.RedisTemplate; 10 | import org.springframework.stereotype.Service; 11 | 12 | import java.text.SimpleDateFormat; 13 | import java.util.ArrayList; 14 | import java.util.Calendar; 15 | import java.util.Date; 16 | import java.util.List; 17 | 18 | @Service 19 | public class DataService { 20 | 21 | @Autowired 22 | private RedisTemplate redisTemplate; 23 | 24 | private SimpleDateFormat df = new SimpleDateFormat("yyyyMMdd"); 25 | 26 | // 将指定的IP计入UV 27 | public void recordUV(String ip) { 28 | String redisKey = RedisKeyUtil.getUVKey(df.format(new Date())); 29 | redisTemplate.opsForHyperLogLog().add(redisKey, ip); 30 | } 31 | 32 | // 统计指定日期范围内的UV 33 | public long calculateUV(Date start, Date end) { 34 | if (start == null || end == null) { 35 | throw new IllegalArgumentException("参数不能为空!"); 36 | } 37 | 38 | // 整理该日期范围内的key 39 | List keyList = new ArrayList<>(); 40 | Calendar calendar = Calendar.getInstance(); 41 | calendar.setTime(start); 42 | while (!calendar.getTime().after(end)) { 43 | String key = RedisKeyUtil.getUVKey(df.format(calendar.getTime())); 44 | keyList.add(key); 45 | calendar.add(Calendar.DATE, 1); 46 | } 47 | 48 | // 合并这些数据 49 | String redisKey = RedisKeyUtil.getUVKey(df.format(start), df.format(end)); 50 | redisTemplate.opsForHyperLogLog().union(redisKey, keyList.toArray()); 51 | 52 | // 返回统计的结果 53 | return redisTemplate.opsForHyperLogLog().size(redisKey); 54 | } 55 | 56 | // 将指定用户计入DAU 57 | public void recordDAU(int userId) { 58 | String redisKey = RedisKeyUtil.getDAUKey(df.format(new Date())); 59 | redisTemplate.opsForValue().setBit(redisKey, userId, true); 60 | } 61 | 62 | // 统计指定日期范围内的DAU 63 | public long calculateDAU(Date start, Date end) { 64 | if (start == null || end == null) { 65 | throw new IllegalArgumentException("参数不能为空!"); 66 | } 67 | 68 | // 整理该日期范围内的key 69 | List keyList = new ArrayList<>(); 70 | Calendar calendar = Calendar.getInstance(); 71 | calendar.setTime(start); 72 | while (!calendar.getTime().after(end)) { 73 | String key = RedisKeyUtil.getDAUKey(df.format(calendar.getTime())); 74 | keyList.add(key.getBytes()); 75 | calendar.add(Calendar.DATE, 1); 76 | } 77 | 78 | // 进行OR运算 79 | return (long) redisTemplate.execute(new RedisCallback() { 80 | @Override 81 | public Object doInRedis(RedisConnection connection) throws DataAccessException { 82 | String redisKey = RedisKeyUtil.getDAUKey(df.format(start), df.format(end)); 83 | connection.bitOp(RedisStringCommands.BitOperation.OR, 84 | redisKey.getBytes(), keyList.toArray(new byte[0][0])); 85 | return connection.bitCount(redisKey.getBytes()); 86 | } 87 | }); 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /src/main/java/com/community/service/LikeService.java: -------------------------------------------------------------------------------- 1 | package com.community.service; 2 | 3 | import com.community.util.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 | redisTemplate.execute(new SessionCallback() { 20 | @Override 21 | public Object execute(RedisOperations operations) throws DataAccessException { 22 | String entityLikeKey = RedisKeyUtil.getEntityLikeKey(entityType, entityId); 23 | String userLikeKey = RedisKeyUtil.getUserLikeKey(entityUserId); 24 | 25 | boolean isMember = operations.opsForSet().isMember(entityLikeKey, userId); 26 | 27 | operations.multi(); 28 | 29 | if (isMember) { 30 | operations.opsForSet().remove(entityLikeKey, userId); 31 | operations.opsForValue().decrement(userLikeKey); 32 | } else { 33 | operations.opsForSet().add(entityLikeKey, userId); 34 | operations.opsForValue().increment(userLikeKey); 35 | } 36 | 37 | return operations.exec(); 38 | } 39 | }); 40 | } 41 | 42 | // 查询某实体点赞的数量 43 | public long findEntityLikeCount(int entityType, int entityId) { 44 | String entityLikeKey = RedisKeyUtil.getEntityLikeKey(entityType, entityId); 45 | return redisTemplate.opsForSet().size(entityLikeKey); 46 | } 47 | 48 | // 查询某人对某实体的点赞状态 49 | public int findEntityLikeStatus(int userId, int entityType, int entityId) { 50 | String entityLikeKey = RedisKeyUtil.getEntityLikeKey(entityType, entityId); 51 | return redisTemplate.opsForSet().isMember(entityLikeKey, userId) ? 1 : 0; 52 | } 53 | 54 | // 查询某个用户获得的赞 55 | public int findUserLikeCount(int userId) { 56 | String userLikeKey = RedisKeyUtil.getUserLikeKey(userId); 57 | Integer count = (Integer) redisTemplate.opsForValue().get(userLikeKey); 58 | return count == null ? 0 : count.intValue(); 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/com/community/service/MessageService.java: -------------------------------------------------------------------------------- 1 | package com.community.service; 2 | 3 | import com.community.dao.MessageMapper; 4 | import com.community.entity.Message; 5 | import com.community.util.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 | @Autowired 18 | private SensitiveFilter sensitiveFilter; 19 | 20 | public List findConversations(int userId, int offset, int limit) { 21 | return messageMapper.selectConversations(userId, offset, limit); 22 | } 23 | 24 | public int findConversationCount(int userId) { 25 | return messageMapper.selectConversationCount(userId); 26 | } 27 | 28 | public List findLetters(String conversationId, int offset, int limit) { 29 | return messageMapper.selectLetters(conversationId, offset, limit); 30 | } 31 | 32 | public int findLetterCount(String conversationId) { 33 | return messageMapper.selectLetterCount(conversationId); 34 | } 35 | 36 | public int findLetterUnreadCount(int userId, String conversationId) { 37 | return messageMapper.selectLetterUnreadCount(userId, conversationId); 38 | } 39 | 40 | public int addMessage(Message message) { 41 | // message.setContent(HtmlUtils.htmlEscape(message.getContent())); 42 | message.setContent(sensitiveFilter.filter(message.getContent())); 43 | return messageMapper.insertMessage(message); 44 | } 45 | 46 | public int readMessage(List ids) { 47 | return messageMapper.updateStatus(ids, 1); 48 | } 49 | 50 | public Message findLatestNotice(int userId, String topic) { 51 | return messageMapper.selectLatestNotice(userId, topic); 52 | } 53 | 54 | public int findNoticeCount(int userId, String topic) { 55 | return messageMapper.selectNoticeCount(userId, topic); 56 | } 57 | 58 | public int findNoticeUnreadCount(int userId, String topic) { 59 | return messageMapper.selectNoticeUnreadCount(userId, topic); 60 | } 61 | 62 | public List findNotices(int userId, String topic, int offset, int limit) { 63 | return messageMapper.selectNotices(userId, topic, offset, limit); 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/com/community/util/CommunityConstant.java: -------------------------------------------------------------------------------- 1 | package com.community.util; 2 | 3 | public interface CommunityConstant { 4 | //constant 常量 activation 激活 5 | 6 | /** 7 | * 激活成功 8 | */ 9 | int ACTIVATION_SUCCESS = 0; 10 | 11 | /** 12 | * 重复激活 13 | */ 14 | int ACTIVATION_REPEAT = 1; 15 | 16 | /** 17 | * 激活失败 18 | */ 19 | int ACTIVATION_FAILURE = 2; 20 | 21 | /** 22 | * 默认状态的登录凭证的超时时间,12小时 23 | */ 24 | int DEFAULT_EXPIRED_SECONDS = 3600 * 12; 25 | 26 | /** 27 | * 记住状态的登录凭证超时时间,100天 28 | */ 29 | int REMEMBER_EXPIRED_SECONDS = 3600 * 24 * 100; 30 | 31 | /** 32 | * 实体类型: 帖子 33 | */ 34 | int ENTITY_TYPE_POST = 1; 35 | 36 | /** 37 | * 实体类型: 评论 38 | */ 39 | int ENTITY_TYPE_COMMENT = 2; 40 | 41 | /** 42 | * 实体类型: 用户 43 | */ 44 | int ENTITY_TYPE_USER = 3; 45 | 46 | /** 47 | * 主题: 评论 48 | */ 49 | String TOPIC_COMMENT = "comment"; 50 | 51 | /** 52 | * 主题: 点赞 53 | */ 54 | String TOPIC_LIKE = "like"; 55 | 56 | /** 57 | * 主题: 关注 58 | */ 59 | String TOPIC_FOLLOW = "follow"; 60 | 61 | /** 62 | * 主题: 发帖 63 | */ 64 | String TOPIC_PUBLISH = "publish"; 65 | 66 | /** 67 | * 主题: 删帖 68 | */ 69 | String TOPIC_DELETE = "delete"; 70 | 71 | /** 72 | * 主题: 分享 73 | */ 74 | String TOPIC_SHARE = "share"; 75 | 76 | /** 77 | * 系统用户ID 这个是数据库中一定要存在的用户,用于发送系统通知。账号system,密码system,一般不登录这个账户,使用这个账户发私信会有bug 78 | */ 79 | int SYSTEM_USER_ID = 1; 80 | 81 | /** 82 | * 权限: 普通用户,数据库对应type 0 83 | */ 84 | String AUTHORITY_USER = "user"; 85 | 86 | /** 87 | * 权限: 管理员,数据库对应type 1 88 | */ 89 | String AUTHORITY_ADMIN = "admin"; 90 | 91 | /** 92 | * 权限: 版主,数据库对应type 2 93 | */ 94 | String AUTHORITY_MODERATOR = "moderator"; 95 | } 96 | -------------------------------------------------------------------------------- /src/main/java/com/community/util/CommunityUtil.java: -------------------------------------------------------------------------------- 1 | package com.community.util; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import org.apache.commons.lang3.StringUtils; 5 | import org.springframework.util.DigestUtils; 6 | 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | import java.util.UUID; 10 | 11 | public class CommunityUtil { 12 | //生成随机字符串,32位 13 | public static String generateUUID(){ 14 | return UUID.randomUUID().toString().replaceAll("-",""); 15 | } 16 | 17 | //MD5加密,采用spring自带的md5加密方法。一般还需要加盐,确保安全 18 | public static String md5(String key){ 19 | //如果密码为null或空格,返回null 20 | if(StringUtils.isBlank(key)){ 21 | return null; 22 | } 23 | return DigestUtils.md5DigestAsHex(key.getBytes()); 24 | } 25 | 26 | public static String getJSONString(int code, String msg, Map map) { 27 | JSONObject json = new JSONObject(); 28 | json.put("code", code); 29 | json.put("msg", msg); 30 | if (map != null) { 31 | for (String key : map.keySet()) { 32 | json.put(key, map.get(key)); 33 | } 34 | } 35 | return json.toJSONString(); 36 | } 37 | 38 | public static String getJSONString(int code, String msg) { 39 | return getJSONString(code, msg, null); 40 | } 41 | 42 | public static String getJSONString(int code) { 43 | return getJSONString(code, null, null); 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/com/community/util/CookieUtil.java: -------------------------------------------------------------------------------- 1 | package com.community.util; 2 | 3 | import javax.servlet.http.Cookie; 4 | import javax.servlet.http.HttpServletRequest; 5 | 6 | //这个工具是从request对象取cookies数组,然后取出ticket 7 | public class CookieUtil { 8 | 9 | public static String getValue(HttpServletRequest request, String name) { 10 | if (request == null || name == null) { 11 | throw new IllegalArgumentException("参数为空!"); 12 | } 13 | 14 | Cookie[] cookies = request.getCookies(); 15 | if (cookies != null) { 16 | for (Cookie cookie : cookies) { 17 | if (cookie.getName().equals(name)) { 18 | return cookie.getValue(); 19 | } 20 | } 21 | } 22 | 23 | return null; 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/community/util/HostHolder.java: -------------------------------------------------------------------------------- 1 | package com.community.util; 2 | 3 | import com.community.entity.User; 4 | import org.springframework.stereotype.Component; 5 | 6 | /** 7 | * 持有用户信息,用于代替session对象. 8 | */ 9 | @Component 10 | //@Component表示放在容器中,由spring进行管理 11 | public class HostHolder { 12 | 13 | private ThreadLocal users = new ThreadLocal<>(); 14 | 15 | public void setUser(User user) { 16 | users.set(user); 17 | } 18 | 19 | public User getUser() { 20 | return users.get(); 21 | } 22 | 23 | public void clear() { 24 | users.remove(); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/community/util/MailClient.java: -------------------------------------------------------------------------------- 1 | package com.community.util; 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.internet.InternetAddress; 12 | import javax.mail.internet.MimeMessage; 13 | 14 | @Component 15 | public class MailClient { 16 | 17 | private static final Logger logger= LoggerFactory.getLogger(MailClient.class); 18 | 19 | @Autowired 20 | private JavaMailSender mailSender; 21 | 22 | @Value("${spring.mail.username}") 23 | private String from; 24 | 25 | public void sendMail(String to,String subject,String content){ 26 | try { 27 | MimeMessage message = mailSender.createMimeMessage(); 28 | MimeMessageHelper helper = new MimeMessageHelper(message); 29 | helper.setFrom(new InternetAddress(from,"community","UTF-8")); 30 | helper.setTo(to); 31 | helper.setSubject(subject); 32 | helper.setText(content, true); 33 | mailSender.send(helper.getMimeMessage()); 34 | } catch (Exception e) { 35 | logger.error("发送邮件失败:" + e.getMessage()); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/community/util/RedisKeyUtil.java: -------------------------------------------------------------------------------- 1 | package com.community.util; 2 | 3 | //生成redis命令的工具类 4 | public class RedisKeyUtil { 5 | 6 | private static final String SPLIT = ":"; 7 | private static final String PREFIX_ENTITY_LIKE = "like:entity"; 8 | private static final String PREFIX_USER_LIKE = "like:user"; 9 | private static final String PREFIX_FOLLOWEE = "followee"; 10 | private static final String PREFIX_FOLLOWER = "follower"; 11 | private static final String PREFIX_KAPTCHA = "kaptcha"; 12 | private static final String PREFIX_CODE = "code"; 13 | private static final String PREFIX_TICKET = "ticket"; 14 | private static final String PREFIX_USER = "user"; 15 | private static final String PREFIX_UV = "uv"; 16 | private static final String PREFIX_DAU = "dau"; 17 | private static final String PREFIX_POST = "post"; 18 | 19 | // 某个实体的赞,实体是帖子或评论 20 | // like:entity:entityType:entityId -> set(userId) 21 | public static String getEntityLikeKey(int entityType, int entityId) { 22 | return PREFIX_ENTITY_LIKE + SPLIT + entityType + SPLIT + entityId; 23 | } 24 | 25 | // 某个用户的赞 26 | // like:user:userId -> int 27 | public static String getUserLikeKey(int userId) { 28 | return PREFIX_USER_LIKE + SPLIT + userId; 29 | } 30 | 31 | // 某个用户关注的实体 32 | // followee:userId:entityType -> zset(entityId,now) 33 | public static String getFolloweeKey(int userId, int entityType) { 34 | return PREFIX_FOLLOWEE + SPLIT + userId + SPLIT + entityType; 35 | } 36 | 37 | // 某个实体拥有的粉丝 38 | // follower:entityType:entityId -> zset(userId,now) 39 | public static String getFollowerKey(int entityType, int entityId) { 40 | return PREFIX_FOLLOWER + SPLIT + entityType + SPLIT + entityId; 41 | } 42 | 43 | // 登录验证码 44 | public static String getKaptchaKey(String owner) { 45 | return PREFIX_KAPTCHA + SPLIT + owner; 46 | } 47 | 48 | // 登录的凭证 49 | public static String getTicketKey(String ticket) { 50 | return PREFIX_TICKET + SPLIT + ticket; 51 | } 52 | 53 | // 用户 54 | public static String getUserKey(int userId) { 55 | return PREFIX_USER + SPLIT + userId; 56 | } 57 | 58 | //日期数据传入年月日就够了,不用时分秒 59 | // 单日UV 60 | public static String getUVKey(String date) { 61 | return PREFIX_UV + SPLIT + date; 62 | } 63 | 64 | // 区间UV 65 | public static String getUVKey(String startDate, String endDate) { 66 | return PREFIX_UV + SPLIT + startDate + SPLIT + endDate; 67 | } 68 | 69 | // 单日活跃用户 70 | public static String getDAUKey(String date) { 71 | return PREFIX_DAU + SPLIT + date; 72 | } 73 | 74 | // 区间活跃用户 75 | public static String getDAUKey(String startDate, String endDate) { 76 | return PREFIX_DAU + SPLIT + startDate + SPLIT + endDate; 77 | } 78 | 79 | // 帖子分数 80 | public static String getPostScoreKey() { 81 | return PREFIX_POST + SPLIT + "score"; 82 | } 83 | 84 | public static String getCodeKey(String owner) { 85 | return PREFIX_CODE + SPLIT + owner; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/main/resources/application-dev.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuumiy/community/c76d7445f2f47867c2ee9124eb331c224be5ae0b/src/main/resources/application-dev.properties -------------------------------------------------------------------------------- /src/main/resources/application-pro.properties: -------------------------------------------------------------------------------- 1 | # ServerProperties 2 | server.port=8080 3 | server.servlet.context-path= 4 | 5 | # ThymeleafProperties 6 | spring.thymeleaf.cache=true 7 | 8 | # DataSourceProperties 9 | spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver 10 | spring.datasource.url=jdbc:mysql://localhost:3306/community?characterEncoding=utf-8&useSSL=false&serverTimezone=Hongkong 11 | spring.datasource.username=root 12 | spring.datasource.password=Nowcoder_123 13 | spring.datasource.type=com.zaxxer.hikari.HikariDataSource 14 | spring.datasource.hikari.maximum-pool-size=15 15 | spring.datasource.hikari.minimum-idle=5 16 | spring.datasource.hikari.idle-timeout=30000 17 | 18 | # MybatisProperties 19 | mybatis.mapper-locations=classpath:mapper/*.xml 20 | mybatis.type-aliases-package=com.community.entity 21 | mybatis.configuration.useGeneratedKeys=true 22 | mybatis.configuration.mapUnderscoreToCamelCase=true 23 | 24 | # MailProperties 25 | spring.mail.host=smtp.sina.com 26 | spring.mail.port=465 27 | spring.mail.username=nowcoder@sina.com 28 | spring.mail.password=nowcoder123 29 | spring.mail.protocol=smtps 30 | spring.mail.properties.mail.smtp.ssl.enable=true 31 | 32 | # community 33 | community.path.domain=http://120.55.53.217 34 | community.path.upload=/tmp/uploads 35 | 36 | # RedisProperties 37 | spring.redis.database=11 38 | spring.redis.host=localhost 39 | spring.redis.port=6379 40 | 41 | # KafkaProperties 42 | spring.kafka.bootstrap-servers=localhost:9092 43 | spring.kafka.consumer.group-id=community-consumer-group 44 | spring.kafka.consumer.enable-auto-commit=true 45 | spring.kafka.consumer.auto-commit-interval=3000 46 | 47 | # ElasticsearchProperties 48 | spring.data.elasticsearch.cluster-name=nowcoder 49 | spring.data.elasticsearch.cluster-nodes=127.0.0.1:9300 50 | 51 | # TaskExecutionProperties 52 | spring.task.execution.pool.core-size=5 53 | spring.task.execution.pool.max-size=15 54 | spring.task.execution.pool.queue-capacity=100 55 | 56 | # TaskSchedulingProperties 57 | spring.task.scheduling.pool.size=5 58 | 59 | # QuartzProperties 60 | spring.quartz.job-store-type=jdbc 61 | spring.quartz.scheduler-name=communityScheduler 62 | spring.quartz.properties.org.quartz.scheduler.instanceId=AUTO 63 | spring.quartz.properties.org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX 64 | spring.quartz.properties.org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate 65 | spring.quartz.properties.org.quartz.jobStore.isClustered=true 66 | spring.quartz.properties.org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool 67 | spring.quartz.properties.org.quartz.threadPool.threadCount=5 68 | 69 | # wk 70 | wk.image.command=/opt/wkhtmltoimage.sh 71 | wk.image.storage=/tmp/wk-images 72 | 73 | # qiniu 74 | qiniu.key.access=6RA-Uus95ZT_1znMrCMD8BpqfjT-K7OKmQTfKB48 75 | qiniu.key.secret=kPNnLFz2_tzztKUVpSLm0lYngtuHWyIq5LzTmLIL 76 | qiniu.bucket.header.name=community_header 77 | quniu.bucket.header.url=http://pvghrij81.bkt.clouddn.com 78 | qiniu.bucket.share.name=community_share 79 | qiniu.bucket.share.url=http://pvghvvuzm.bkt.clouddn.com 80 | 81 | # caffeine 82 | caffeine.posts.max-size=15 83 | caffeine.posts.expire-seconds=180 84 | 85 | # actuator 86 | management.endpoints.web.exposure.include=* 87 | management.endpoints.web.exposure.exclude=info,caches 88 | -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | # profile 2 | spring.profiles.active=dev 3 | 4 | # logback 5 | logging.config=classpath:logback-spring-${spring.profiles.active}.xml -------------------------------------------------------------------------------- /src/main/resources/logback-spring-dev.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/resources/logback-spring-pro.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/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 | 12 | user_id, entity_type, entity_id, target_id, content, status, create_time 13 | 14 | 15 | 16 | 25 | 26 | 33 | 34 | 35 | insert into comment() 36 | values(#{userId},#{entityType},#{entityId},#{targetId},#{content},#{status},#{createTime}) 37 | 38 | 39 | 44 | 45 | 52 | 53 | 62 | 63 | 64 | update comment set status = #{status} where entity_id = #{entityId} and entity_type=1 65 | 66 | 67 | -------------------------------------------------------------------------------- /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 | 30 | 31 | 39 | 40 | 41 | insert into discuss_post() 42 | values(#{userId},#{title},#{content},#{type},#{status},#{createTime},#{commentCount},#{score}) 43 | 44 | 45 | 50 | 51 | 52 | update discuss_post set comment_count = #{commentCount} where id = #{id} 53 | 54 | 55 | 56 | update discuss_post set type = #{type} where id = #{id} 57 | 58 | 59 | 60 | update discuss_post set status = #{status} where id = #{id} 61 | 62 | 63 | 64 | update discuss_post set score = #{score} where id = #{id} 65 | 66 | 67 | -------------------------------------------------------------------------------- /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 | 16 | 29 | 30 | 39 | 40 | 49 | 50 | 57 | 58 | 68 | 69 | 70 | insert into message() 71 | values(#{fromId},#{toId},#{conversationId},#{content},#{status},#{createTime}) 72 | 73 | 74 | 75 | update message set status = #{status} 76 | where id in 77 | 78 | #{id} 79 | 80 | 81 | 82 | 93 | 94 | 101 | 102 | 111 | 112 | 122 | 123 | -------------------------------------------------------------------------------- /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/resources/sensitive-words.txt: -------------------------------------------------------------------------------- 1 | 赌博 2 | 嫖娼 3 | 吸毒 4 | 开票 -------------------------------------------------------------------------------- /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/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 { 24 | padding: 5px 0; 25 | font-size: 16px; 26 | } 27 | 28 | header .badge { 29 | position: absolute; 30 | top: -3px; 31 | left: 33px; 32 | } 33 | 34 | footer { 35 | padding: 20px 0; 36 | font-size: 12px; 37 | position: absolute; 38 | bottom: 0; 39 | width: 100%; 40 | } 41 | 42 | footer .qrcode { 43 | text-align: center; 44 | } 45 | 46 | footer .detail-info{ 47 | border-left: 1px solid #888; 48 | } 49 | 50 | footer .company-info li { 51 | padding-left: 16px; 52 | margin: 4px 0; 53 | } 54 | 55 | .main { 56 | padding: 20px 0; 57 | padding-bottom: 200px; 58 | } 59 | 60 | .main .container { 61 | background: #fff; 62 | padding: 20px; 63 | } 64 | 65 | i { 66 | font-style: normal; 67 | } 68 | 69 | u { 70 | text-decoration: none; 71 | } 72 | 73 | b { 74 | font-weight: normal; 75 | } 76 | 77 | a { 78 | color: #000; 79 | } 80 | 81 | a:hover { 82 | text-decoration: none; 83 | } 84 | 85 | .font-size-12 { 86 | font-size: 12px; 87 | } 88 | .font-size-14 { 89 | font-size: 14px; 90 | } 91 | .font-size-16 { 92 | font-size: 16px; 93 | } 94 | .font-size-18 { 95 | font-size: 18px; 96 | } 97 | .font-size-20 { 98 | font-size: 20px; 99 | } 100 | .font-size-22 { 101 | font-size: 20px; 102 | } 103 | .font-size-24 { 104 | font-size: 20px; 105 | } 106 | 107 | .hidden { 108 | display: none; 109 | } 110 | 111 | .rt-0 { 112 | right: 0; 113 | top: 0; 114 | } 115 | 116 | .square { 117 | display: inline-block; 118 | width: 7px; 119 | height: 7px; 120 | background: #ff6547; 121 | margin-bottom: 2px; 122 | margin-right: 3px; 123 | } 124 | 125 | .bg-gray { 126 | background: #eff0f2; 127 | } 128 | 129 | .user-header { 130 | width: 50px; 131 | height: 50px; 132 | } 133 | 134 | em { 135 | font-style: normal; 136 | color: red; 137 | } 138 | -------------------------------------------------------------------------------- /src/main/resources/static/css/letter.css: -------------------------------------------------------------------------------- 1 | .main .nav .badge { 2 | position: absolute; 3 | top: -3px; 4 | left: 68px; 5 | } 6 | 7 | .main .media .badge { 8 | position: absolute; 9 | top: 12px; 10 | left: -3px; 11 | } 12 | 13 | .toast { 14 | max-width: 100%; 15 | width: 80%; 16 | } -------------------------------------------------------------------------------- /src/main/resources/static/css/login.css: -------------------------------------------------------------------------------- 1 | .main .container { 2 | width: 720px; 3 | } 4 | -------------------------------------------------------------------------------- /src/main/resources/static/html/ajax-demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | AJAX 6 | 7 | 8 | 9 |

10 | 11 |

12 | 13 | 14 | 31 | 32 | -------------------------------------------------------------------------------- /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/img/404.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuumiy/community/c76d7445f2f47867c2ee9124eb331c224be5ae0b/src/main/resources/static/img/404.png -------------------------------------------------------------------------------- /src/main/resources/static/img/error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuumiy/community/c76d7445f2f47867c2ee9124eb331c224be5ae0b/src/main/resources/static/img/error.png -------------------------------------------------------------------------------- /src/main/resources/static/img/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yuumiy/community/c76d7445f2f47867c2ee9124eb331c224be5ae0b/src/main/resources/static/img/icon.png -------------------------------------------------------------------------------- /src/main/resources/static/js/discuss.js: -------------------------------------------------------------------------------- 1 | $(function(){ 2 | $("#topBtn").click(setTop); 3 | $("#wonderfulBtn").click(setWonderful); 4 | $("#deleteBtn").click(setDelete); 5 | }); 6 | 7 | function like(btn, entityType, entityId, entityUserId, postId) { 8 | $.post( 9 | CONTEXT_PATH + "/like", 10 | {"entityType":entityType,"entityId":entityId,"entityUserId":entityUserId,"postId":postId}, 11 | function(data) { 12 | data = $.parseJSON(data); 13 | if(data.code == 0) { 14 | $(btn).children("i").text(data.likeCount); 15 | $(btn).children("b").text(data.likeStatus==1?'已赞':"赞"); 16 | } else { 17 | alert(data.msg); 18 | } 19 | } 20 | ); 21 | } 22 | 23 | // 置顶 24 | function setTop() { 25 | if($("#topBtn").hasClass("btn-danger")) { 26 | //置顶 27 | $.post( 28 | CONTEXT_PATH + "/discuss/top", 29 | {"id": $("#postId").val()}, 30 | function (data) { 31 | //从服务器返回的json字符串中取出code值 32 | data = $.parseJSON(data); 33 | if (data.code == 0) { 34 | window.location.reload(); 35 | } else { 36 | alert(data.msg); 37 | } 38 | } 39 | ); 40 | }else{ 41 | //取消置顶 42 | $.post( 43 | CONTEXT_PATH + "/discuss/untop", 44 | {"id": $("#postId").val()}, 45 | function (data) { 46 | //从服务器返回的json字符串中取出code值 47 | data = $.parseJSON(data); 48 | if (data.code == 0) { 49 | window.location.reload(); 50 | } else { 51 | alert(data.msg); 52 | } 53 | } 54 | ); 55 | } 56 | } 57 | 58 | // 加精 59 | function setWonderful() { 60 | if($("#wonderfulBtn").hasClass("btn-danger")) { 61 | //加精 62 | $.post( 63 | CONTEXT_PATH + "/discuss/wonderful", 64 | {"id": $("#postId").val()}, 65 | function (data) { 66 | data = $.parseJSON(data); 67 | if (data.code == 0) { 68 | window.location.reload(); 69 | } else { 70 | alert(data.msg); 71 | } 72 | } 73 | ); 74 | }else{ 75 | //取消加精 76 | $.post( 77 | CONTEXT_PATH + "/discuss/unwonderful", 78 | {"id": $("#postId").val()}, 79 | function (data) { 80 | data = $.parseJSON(data); 81 | if (data.code == 0) { 82 | window.location.reload(); 83 | } else { 84 | alert(data.msg); 85 | } 86 | } 87 | ); 88 | } 89 | } 90 | 91 | // 删除 92 | function setDelete() { 93 | $.post( 94 | CONTEXT_PATH + "/discuss/delete", 95 | {"id":$("#postId").val()}, 96 | function(data) { 97 | data = $.parseJSON(data); 98 | if(data.code == 0) { 99 | location.href = CONTEXT_PATH + "/index"; 100 | } else { 101 | alert(data.msg); 102 | } 103 | } 104 | ); 105 | } -------------------------------------------------------------------------------- /src/main/resources/static/js/forget.js: -------------------------------------------------------------------------------- 1 | $(function(){ 2 | $(".feach-btn").click(feach); 3 | }); 4 | 5 | function feach() { 6 | var btn = this; 7 | var count = 60; 8 | const countDown = setInterval(() => { 9 | if(count===60){ 10 | $.post( 11 | CONTEXT_PATH + "/user/sendCode?p="+Math.random(), 12 | {"email":$("#email").val()}, 13 | function(data) { 14 | data = $.parseJSON(data); 15 | if(data.code != 0) { 16 | alert(data.msg); 17 | } 18 | } 19 | ); 20 | } 21 | if (count === 0) { 22 | $(btn).text('重新发送').removeAttr('disabled'); 23 | $(btn).css({ 24 | background: '#ff9400', 25 | color: '#fff', 26 | }); 27 | clearInterval(countDown); 28 | } else { 29 | $(btn).attr('disabled', true); 30 | $(btn).css({ 31 | background: '#d8d8d8', 32 | color: '#707070', 33 | }); 34 | $(btn).text(count + '秒后可重新获取'); 35 | } 36 | count--; 37 | }, 1000); 38 | } 39 | -------------------------------------------------------------------------------- /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/resources/static/js/index.js: -------------------------------------------------------------------------------- 1 | $(function(){ 2 | $("#publishBtn").click(publish); 3 | }); 4 | 5 | function publish() { 6 | $("#publishModal").modal("hide"); 7 | 8 | // 发送AJAX请求之前,将CSRF令牌设置到请求的消息头中. 9 | // var token = $("meta[name='_csrf']").attr("content"); 10 | // // var header = $("meta[name='_csrf_header']").attr("content"); 11 | // // $(document).ajaxSend(function(e, xhr, options){ 12 | // // xhr.setRequestHeader(header, token); 13 | // // }); 14 | 15 | // 获取标题和内容 16 | var title = $("#recipient-name").val(); 17 | var content = $("#message-text").val().replace(/\r\n/g, '
').replace(/\n/g, '
').replace(/\s/g, ' '); 18 | // 发送异步请求(POST) 19 | $.post( 20 | CONTEXT_PATH + "/discuss/add", 21 | {"title":title,"content":content}, 22 | function(data) { 23 | data = $.parseJSON(data); 24 | // 在提示框中显示返回消息 25 | $("#hintBody").text(data.msg); 26 | // 显示提示框 27 | $("#hintModal").modal("show"); 28 | // 2秒后,自动隐藏提示框 29 | setTimeout(function(){ 30 | $("#hintModal").modal("hide"); 31 | // 刷新页面 32 | if(data.code == 0) { 33 | window.location.reload(); 34 | } 35 | }, 2000); 36 | } 37 | ); 38 | 39 | } -------------------------------------------------------------------------------- /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().replace(/\r\n/g, '
').replace(/\n/g, '
').replace(/\s/g, ' '); 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/resources/static/js/profile.js: -------------------------------------------------------------------------------- 1 | //在profile.html中,通过button的class属性获取对象 2 | $(function(){ 3 | $(".follow-btn").click(follow); 4 | }); 5 | 6 | 7 | function follow() { 8 | var btn = this; 9 | if($(btn).hasClass("btn-info")) { 10 | // 关注TA $(btn).prev()获取前一个结点的值,就是用户id 11 | $.post( 12 | CONTEXT_PATH + "/follow", 13 | {"entityType":3,"entityId":$(btn).prev().val()}, 14 | function(data) { 15 | data = $.parseJSON(data); 16 | if(data.code == 0) { 17 | window.location.reload(); 18 | } else { 19 | alert(data.msg); 20 | } 21 | } 22 | ); 23 | // $(btn).text("已关注").removeClass("btn-info").addClass("btn-secondary"); 24 | } else { 25 | // 取消关注 26 | $.post( 27 | CONTEXT_PATH + "/unfollow", 28 | {"entityType":3,"entityId":$(btn).prev().val()}, 29 | function(data) { 30 | data = $.parseJSON(data); 31 | if(data.code == 0) { 32 | window.location.reload(); 33 | } else { 34 | alert(data.msg); 35 | } 36 | } 37 | ); 38 | //$(btn).text("关注TA").removeClass("btn-secondary").addClass("btn-info"); 39 | } 40 | } -------------------------------------------------------------------------------- /src/main/resources/static/js/setting.js: -------------------------------------------------------------------------------- 1 | $(function(){ 2 | $("#uploadForm").submit(upload); 3 | }); 4 | 5 | //$.ajax是一种比$.post更为强大的异步请求方法,因为文件所需的参数更多了 6 | function upload() { 7 | $.ajax({ 8 | url: "http://upload-z1.qiniup.com", 9 | method: "post", 10 | processData: false, 11 | contentType: false, 12 | data: new FormData($("#uploadForm")[0]), 13 | success: function(data) { 14 | if(data && data.code == 0) { 15 | // 更新头像访问路径 16 | $.post( 17 | CONTEXT_PATH + "/user/header/url", 18 | {"fileName":$("input[name='key']").val()}, 19 | function(data) { 20 | data = $.parseJSON(data); 21 | if(data.code == 0) { 22 | window.location.reload(); 23 | } else { 24 | alert(data.msg); 25 | } 26 | } 27 | ); 28 | } else { 29 | alert("上传失败!"); 30 | } 31 | } 32 | }); 33 | return false; 34 | } -------------------------------------------------------------------------------- /src/main/resources/templates/demo/view.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Teacher 6 | 7 | 8 |

9 |

10 | 11 | -------------------------------------------------------------------------------- /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 | 18 | 19 |
20 |
21 | 22 |
23 |
24 | 25 | 26 |
27 | 28 |
29 |
30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /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 | 18 | 19 |
20 |
21 | 22 |
23 |
24 | 25 | 26 |
27 | 28 |
29 |
30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/main/resources/templates/mail/activation.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 激活账号 6 | 7 | 8 |
9 |

10 | xxx@xxx.com, 您好! 11 |

12 |

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

17 |
18 | 19 | -------------------------------------------------------------------------------- /src/main/resources/templates/mail/demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 邮件实例 6 | 7 | 8 |

欢迎你,!

9 | 10 | -------------------------------------------------------------------------------- /src/main/resources/templates/mail/forget.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 忘记密码 6 | 7 | 8 |
9 |

10 | xxx@xxx.com, 您好! 11 |

12 |

13 | 您正在找回Community的密码, 本次操作的验证码为 u5s6dt , 14 | 有效时间10分钟, 请您及时进行操作! 15 |

16 |
17 | 18 | -------------------------------------------------------------------------------- /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 | 18 | 19 |
20 | 21 |
22 |
网站 UV
23 |
24 | 25 | 26 | 27 |
28 |
    29 |
  • 30 | 统计结果 31 | 0 32 |
  • 33 |
34 |
35 | 36 |
37 |
活跃用户
38 |
39 | 40 | 41 | 42 |
43 |
    44 |
  • 45 | 统计结果 46 | 0 47 |
  • 48 |
49 |
50 |
51 | 52 | 53 |
54 | 55 |
56 |
57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /src/main/resources/templates/site/followee.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 关注 10 | 11 | 12 |
13 | 14 |
15 | 16 |
17 | 18 | 19 |
20 |
21 |
22 | 23 | 35 | 返回个人主页> 36 |
37 | 38 | 39 |
    40 |
  • 41 | 42 | 用户头像 43 | 44 |
    45 |
    46 | 落基山脉下的闲人 47 | 48 | 关注于 2019-04-28 14:13:25 49 | 50 |
    51 |
    52 | 53 | 55 |
    56 |
    57 |
  • 58 |
59 | 60 | 63 |
64 |
65 | 66 | 67 |
68 | 69 |
70 |
71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /src/main/resources/templates/site/follower.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 关注 10 | 11 | 12 |
13 | 14 |
15 | 16 |
17 | 18 | 19 |
20 |
21 |
22 | 23 | 35 | 返回个人主页> 36 |
37 | 38 | 39 |
    40 |
  • 41 | 42 | 用户头像 43 | 44 |
    45 |
    46 | 落基山脉下的闲人 47 | 48 | 关注于 2019-04-28 14:13:25 49 | 50 |
    51 |
    52 | 53 | 55 |
    56 |
    57 |
  • 58 |
59 | 60 | 63 |
64 |
65 | 66 | 67 |
68 | 69 |
70 |
71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /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 | 19 | 20 |
21 |
22 |
23 |
24 | 25 |
26 | 30 |
31 | 该邮箱已注册! 32 |
33 |
34 |
35 |
36 | 37 |
38 | 40 |
41 | 验证码不正确! 42 |
43 |
44 |
45 | 46 |
47 |
48 |
49 | 50 |
51 | 52 |
53 | 密码长度不能小于8位! 54 |
55 |
56 |
57 |
58 |
59 |
60 | 61 |
62 |
63 |
64 |
65 |
66 | 67 | 68 |
69 | 70 |
71 |
72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /src/main/resources/templates/site/letter-detail.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 私信详情 11 | 12 | 13 |
14 | 15 |
16 | 17 |
18 | 19 | 20 |
21 |
22 |
23 |
24 |
来自 落基山脉下的闲人 的私信
25 |
26 |
27 | 28 | 29 |
30 |
31 | 32 | 60 | 61 | 73 | 74 | 75 |
    76 |
  • 77 | 78 | 用户头像 79 | 80 | 92 |
  • 93 |
94 | 95 | 98 |
99 |
100 | 101 | 102 |
103 | 104 |
105 |
106 | 107 | 108 | 109 | 110 | 111 | 112 | 117 | 118 | 119 | -------------------------------------------------------------------------------- /src/main/resources/templates/site/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 登录 11 | 12 | 13 |
14 | 15 |
16 | 17 |
18 | 19 | 20 |
21 |
22 |

登  录

23 |
24 |
25 | 26 |
27 | 31 |
32 | 该账号不存在! 33 |
34 |
35 |
36 |
37 | 38 |
39 | 40 | 44 |
45 | 密码长度不能小于8位! 46 |
47 |
48 |
49 |
50 | 51 |
52 | 54 |
55 | 验证码不正确! 56 |
57 |
58 |
59 | 60 | 刷新验证码 61 |
62 |
63 |
64 |
65 |
66 | 68 | 69 | 忘记密码? 70 |
71 |
72 |
73 |
74 |
75 | 76 |
77 |
78 |
79 |
80 |
81 | 82 | 83 |
84 | 85 |
86 |
87 | 88 | 89 | 90 | 91 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /src/main/resources/templates/site/my-comment.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 我的评论 10 | 11 | 12 |
13 | 14 |
15 | 16 |
17 | 18 | 19 |
20 |
21 | 22 |
23 | 34 | 返回个人主页> 35 |
36 | 37 |
38 |
评论的帖子()
39 |
    40 |
  • 41 |
    42 | 44 |
    45 |
    46 |
    47 | 评论于 48 |
    49 |
  • 50 |
51 | 52 | 55 |
56 |
57 |
58 | 59 | 60 |
61 | 62 |
63 |
64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /src/main/resources/templates/site/my-post.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 我的帖子 10 | 11 | 12 |
13 | 14 |
15 | 16 |
17 | 18 | 19 |
20 |
21 | 22 |
23 | 34 | 返回个人主页> 35 |
36 | 37 |
38 |
发布的帖子()
39 | 52 | 53 | 56 |
57 |
58 |
59 | 60 | 61 |
62 | 63 |
64 |
65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /src/main/resources/templates/site/notice-detail.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 通知详情 11 | 12 | 13 |
14 | 15 |
16 | 17 |
18 | 19 | 20 |
21 |
22 |
23 |
24 |
系统通知
25 |
26 |
27 | 28 |
29 |
30 | 31 | 32 |
    33 |
  • 34 | 系统图标 35 | 64 |
  • 65 |
66 | 67 | 70 |
71 |
72 | 73 | 74 |
75 | 76 |
77 |
78 | 79 | 80 | 81 | 82 | 83 | 84 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /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 |

您的账号已经激活成功,可以正常使用了!

24 |
25 |

26 | 系统会在 8 秒后自动跳转, 27 | 您也可以点此 链接, 跳转到首页! 28 |

29 |
30 |
31 |
32 | 33 | 34 |
35 | 36 |
37 |
38 | 39 | 40 | 41 | 42 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /src/main/resources/templates/site/operate.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 操作结果 11 | 12 | 13 |
14 | 15 |
16 | 17 |
18 | 19 | 20 |
21 |
22 |
23 |

您的账号已经激活成功,可以正常使用了!

24 |
25 |
26 |
27 |
28 | 29 | 30 |
31 | 32 |
33 |
34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/main/resources/templates/site/profile.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 个人主页 10 | 11 | 12 |
13 | 14 |
15 | 16 |
17 | 18 | 19 |
20 |
21 | 22 |
23 | 34 |
35 | 36 |
37 | 用户头像 38 |
39 |
40 | user 41 | 42 | 44 |
45 |
46 | 注册于 2015-06-12 15:20:12 47 |
48 |
49 | 关注了 5 50 | 关注者 123 51 | 获得了 87 个赞 52 |
53 |
54 |
55 |
56 |
57 | 58 | 59 |
60 | 61 |
62 |
63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /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 | 19 | 20 |
21 |
22 |

注  册

23 |
24 |
25 | 26 |
27 | 28 | 32 |
33 | 该账号已存在! 34 |
35 |
36 |
37 |
38 | 39 |
40 | 44 |
45 | 密码长度不能小于8位! 46 |
47 |
48 |
49 |
50 | 51 |
52 | 56 |
57 | 两次输入的密码不一致! 58 |
59 |
60 |
61 |
62 | 63 |
64 | 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 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /src/main/resources/templates/site/search.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 搜索结果 10 | 11 | 12 |
13 | 14 |
15 | 16 |
17 | 18 | 19 |
20 |
21 |
相关帖子
22 | 23 |
    24 |
  • 25 | 用户头像 26 |
    27 |
    28 | 备战春招,面试刷题跟他复习,一个月全搞定! 29 |
    30 |
    31 | 金三银四的金三已经到了,你还沉浸在过年的喜悦中吗? 如果是,那我要让你清醒一下了:目前大部分公司已经开启了内推,正式网申也将在3月份陆续开始,金三银四,春招的求职黄金时期已经来啦!!! 再不准备,作为19应届生的你可能就找不到工作了。。。作为20届实习生的你可能就找不到实习了。。。 现阶段时间紧,任务重,能做到短时间内快速提升的也就只有算法了, 那么算法要怎么复习?重点在哪里?常见笔试面试算法题型和解题思路以及最优代码是怎样的? 跟左程云老师学算法,不仅能解决以上所有问题,还能在短时间内得到最大程度的提升!!! 32 |
    33 |
    34 | 寒江雪 35 | 发布于 2019-04-15 15:32:18 36 |
      37 |
    • 11
    • 38 |
    • |
    • 39 |
    • 回复 7
    • 40 |
    41 |
    42 |
    43 |
  • 44 |
45 | 46 | 49 |
50 |
51 | 52 | 53 |
54 | 55 |
56 |
57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /src/test/java/com/community/BlockingQueueTests.java: -------------------------------------------------------------------------------- 1 | package com.community; 2 | 3 | import java.util.Random; 4 | import java.util.concurrent.ArrayBlockingQueue; 5 | import java.util.concurrent.BlockingQueue; 6 | 7 | public class BlockingQueueTests { 8 | 9 | public static void main(String[] args) { 10 | BlockingQueue queue = new ArrayBlockingQueue(10); 11 | new Thread(new Producer(queue)).start(); 12 | new Thread(new Consumer(queue)).start(); 13 | new Thread(new Consumer(queue)).start(); 14 | new Thread(new Consumer(queue)).start(); 15 | } 16 | 17 | } 18 | 19 | class Producer implements Runnable { 20 | 21 | private BlockingQueue queue; 22 | 23 | public Producer(BlockingQueue queue) { 24 | this.queue = queue; 25 | } 26 | 27 | @Override 28 | public void run() { 29 | try { 30 | for (int i = 0; i < 100; i++) { 31 | Thread.sleep(20); 32 | queue.put(i); 33 | System.out.println(Thread.currentThread().getName() + "生产:" + queue.size()); 34 | } 35 | } catch (Exception e) { 36 | e.printStackTrace(); 37 | } 38 | } 39 | 40 | } 41 | 42 | class Consumer implements Runnable { 43 | 44 | private BlockingQueue queue; 45 | 46 | public Consumer(BlockingQueue queue) { 47 | this.queue = queue; 48 | } 49 | 50 | @Override 51 | public void run() { 52 | try { 53 | while (true) { 54 | Thread.sleep(new Random().nextInt(1000)); 55 | queue.take(); 56 | System.out.println(Thread.currentThread().getName() + "消费:" + queue.size()); 57 | } 58 | } catch (Exception e) { 59 | e.printStackTrace(); 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /src/test/java/com/community/CommunityApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.community; 2 | 3 | import com.community.dao.AlphaDao; 4 | import com.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/test/java/com/community/KafkaTests.java: -------------------------------------------------------------------------------- 1 | package com.community; 2 | 3 | import org.apache.kafka.clients.consumer.ConsumerRecord; 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.kafka.annotation.KafkaListener; 9 | import org.springframework.kafka.core.KafkaTemplate; 10 | import org.springframework.stereotype.Component; 11 | import org.springframework.test.context.ContextConfiguration; 12 | import org.springframework.test.context.junit4.SpringRunner; 13 | 14 | @RunWith(SpringRunner.class) 15 | @SpringBootTest 16 | @ContextConfiguration(classes = CommunityApplication.class) 17 | public class KafkaTests { 18 | 19 | @Autowired 20 | private KafkaProducer kafkaProducer; 21 | 22 | @Test 23 | public void testKafka() { 24 | kafkaProducer.sendMessage("test", "Hello!"); 25 | kafkaProducer.sendMessage("test", "Are you there?"); 26 | 27 | //生产者发消息是我们主动去调的,消费者消费消息是被动去调的。因为阻塞了10s(try catch),所以程序10s后才结束 28 | try { 29 | //1000ms*10 10s 30 | Thread.sleep(1000 * 10); 31 | } catch (InterruptedException e) { 32 | e.printStackTrace(); 33 | } 34 | } 35 | 36 | } 37 | 38 | @Component 39 | class KafkaProducer { 40 | 41 | @Autowired 42 | private KafkaTemplate kafkaTemplate; 43 | 44 | public void sendMessage(String topic, String content) { 45 | kafkaTemplate.send(topic, content); 46 | } 47 | 48 | } 49 | 50 | @Component 51 | class KafkaConsumer { 52 | 53 | @KafkaListener(topics = {"test"}) 54 | public void handleMessage(ConsumerRecord record) { 55 | System.out.println(record.value()); 56 | } 57 | 58 | 59 | } -------------------------------------------------------------------------------- /src/test/java/com/community/LoggerTests.java: -------------------------------------------------------------------------------- 1 | package com.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/test/java/com/community/MailTests.java: -------------------------------------------------------------------------------- 1 | package com.community; 2 | 3 | import com.community.util.MailClient; 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 | import org.thymeleaf.TemplateEngine; 11 | import org.thymeleaf.context.Context; 12 | 13 | @RunWith(SpringRunner.class) 14 | @SpringBootTest 15 | @ContextConfiguration(classes = CommunityApplication.class) 16 | public class MailTests { 17 | @Autowired 18 | private MailClient mailClient; 19 | 20 | @Autowired 21 | private TemplateEngine templateEngine; 22 | 23 | @Test 24 | public void testEmailSend(){ 25 | mailClient.sendMail("david_zhongdawei@163.com","test","welcome"); 26 | } 27 | 28 | @Test 29 | public void testHtmlEmail(){ 30 | Context context = new Context(); 31 | context.setVariable("username", "sunday"); 32 | 33 | String content = templateEngine.process("/mail/demo", context); 34 | System.out.println(content); 35 | 36 | mailClient.sendMail("david_zhongdawei@163.com", "HTML", content); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/test/java/com/community/MapperTests.java: -------------------------------------------------------------------------------- 1 | package com.community; 2 | 3 | import com.community.dao.LoginTicketMapper; 4 | import com.community.entity.DiscussPost; 5 | import com.community.entity.LoginTicket; 6 | import com.community.entity.User; 7 | import com.community.dao.DiscussPostMapper; 8 | import com.community.dao.UserMapper; 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 | import java.util.UUID; 19 | 20 | @RunWith(SpringRunner.class) 21 | @SpringBootTest 22 | @ContextConfiguration(classes = CommunityApplication.class) 23 | public class MapperTests { 24 | 25 | @Autowired 26 | private UserMapper userMapper; 27 | @Autowired 28 | private DiscussPostMapper discussPostMapper; 29 | @Autowired 30 | private LoginTicketMapper loginTicketMapper; 31 | 32 | @Test 33 | public void testSelectUser() { 34 | User user = userMapper.selectById(101); 35 | System.out.println(user); 36 | 37 | user = userMapper.selectByName("liubei"); 38 | System.out.println(user); 39 | 40 | user = userMapper.selectByEmail("nowcoder101@sina.com"); 41 | System.out.println(user); 42 | } 43 | 44 | @Test 45 | public void testInsertUser() { 46 | User user = new User(); 47 | user.setUsername("test"); 48 | user.setPassword("123456"); 49 | user.setSalt("abc"); 50 | user.setEmail("test@qq.com"); 51 | user.setHeaderUrl("http://www.nowcoder.com/101.png"); 52 | user.setCreateTime(new Date()); 53 | 54 | int rows=userMapper.insertUser(user); 55 | System.out.println(rows); 56 | System.out.println(user.getId()); 57 | } 58 | 59 | @Test 60 | public void updateUser() { 61 | int rows=userMapper.updateStatus(150, 1); 62 | System.out.println(rows); 63 | 64 | rows = userMapper.updateHeader(150, "http://www.nowcoder.com/102.png"); 65 | System.out.println(rows); 66 | 67 | rows = userMapper.updatePassword(150, "hello"); 68 | System.out.println(rows); 69 | } 70 | 71 | @Test 72 | public void testSelectPosts() { 73 | List list = discussPostMapper.selectDiscussPosts(149, 0, 10,0); 74 | for(DiscussPost post : list) { 75 | System.out.println(post); 76 | } 77 | 78 | int rows = discussPostMapper.selectDiscussPostRows(149); 79 | System.out.println(rows); 80 | } 81 | 82 | @Test 83 | public void testInsertLoginTicket() { 84 | LoginTicket loginTicket = new LoginTicket(); 85 | loginTicket.setUserId(101); 86 | loginTicket.setTicket("abc"); 87 | loginTicket.setStatus(0); 88 | loginTicket.setExpired(new Date(System.currentTimeMillis() + 1000 * 60 * 10)); 89 | 90 | loginTicketMapper.insertLoginTicket(loginTicket); 91 | } 92 | 93 | @Test 94 | public void testSelectLoginTicket() { 95 | LoginTicket loginTicket = loginTicketMapper.selectByTicket("abc"); 96 | System.out.println(loginTicket); 97 | 98 | loginTicketMapper.updateStatus("abc", 1); 99 | loginTicket = loginTicketMapper.selectByTicket("abc"); 100 | System.out.println(loginTicket); 101 | } 102 | @Test 103 | public void testDate(){ 104 | System.out.println(new Date(System.currentTimeMillis() + 3600 * 2400 * 1000)); 105 | System.out.println(UUID.randomUUID().toString().replaceAll("-","")); 106 | System.out.println("a.png".substring("a.png".lastIndexOf("."))); 107 | } 108 | 109 | } 110 | -------------------------------------------------------------------------------- /src/test/java/com/community/QuartzTests.java: -------------------------------------------------------------------------------- 1 | package com.community; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.quartz.JobKey; 6 | import org.quartz.Scheduler; 7 | import org.quartz.SchedulerException; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.boot.test.context.SpringBootTest; 10 | import org.springframework.test.context.ContextConfiguration; 11 | import org.springframework.test.context.junit4.SpringRunner; 12 | 13 | @RunWith(SpringRunner.class) 14 | @SpringBootTest 15 | @ContextConfiguration(classes = CommunityApplication.class) 16 | public class QuartzTests { 17 | 18 | @Autowired 19 | private Scheduler scheduler; 20 | 21 | @Test 22 | public void testDeleteJob() { 23 | try { 24 | boolean result = scheduler.deleteJob(new JobKey("alphaJob", "alphaJobGroup")); 25 | System.out.println(result); 26 | } catch (SchedulerException e) { 27 | e.printStackTrace(); 28 | } 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/test/java/com/community/SensitiveTests.java: -------------------------------------------------------------------------------- 1 | package com.community; 2 | 3 | import com.community.util.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 SensitiveTests { 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 | text = "这里可以☆赌☆博☆,可以☆嫖☆娼☆,可以☆吸☆毒☆,可以☆开☆票☆,哈哈哈!"; 26 | text = sensitiveFilter.filter(text); 27 | System.out.println(text); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/test/java/com/community/SortTests.java: -------------------------------------------------------------------------------- 1 | package com.community; 2 | 3 | import java.util.Arrays; 4 | 5 | public class SortTests { 6 | public static void main(String[] args) { 7 | int arr[]={50,90,-8,100,-78,5}; 8 | quickSort(arr,0,arr.length-1); 9 | System.out.println(Arrays.toString(arr)); 10 | } 11 | 12 | public static void quickSort(int[] arr, int left, int right) { 13 | int l=left; 14 | int r=right; 15 | int pivot=arr[(left+right)/2]; 16 | int temp=0; 17 | while (lpivot) 21 | r--; 22 | if(l>=r) 23 | break; 24 | temp=arr[l]; 25 | arr[l]=arr[r]; 26 | arr[r]=temp; 27 | if(arr[l]==pivot) 28 | r--; 29 | if(arr[r]==pivot) 30 | l++; 31 | } 32 | if(l==r){ 33 | r--; 34 | l++; 35 | } 36 | if(leftl){ 40 | quickSort(arr,l,right); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/test/java/com/community/ThreadPoolTests.java: -------------------------------------------------------------------------------- 1 | package com.community; 2 | 3 | import com.community.service.AlphaService; 4 | import org.junit.Test; 5 | import org.junit.runner.RunWith; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.boot.test.context.SpringBootTest; 10 | import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; 11 | import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; 12 | import org.springframework.test.context.ContextConfiguration; 13 | import org.springframework.test.context.junit4.SpringRunner; 14 | 15 | import java.util.Date; 16 | import java.util.concurrent.ExecutorService; 17 | import java.util.concurrent.Executors; 18 | import java.util.concurrent.ScheduledExecutorService; 19 | import java.util.concurrent.TimeUnit; 20 | 21 | @RunWith(SpringRunner.class) 22 | @SpringBootTest 23 | @ContextConfiguration(classes = CommunityApplication.class) 24 | public class ThreadPoolTests { 25 | 26 | private static final Logger logger = LoggerFactory.getLogger(ThreadPoolTests.class); 27 | 28 | // JDK普通线程池 29 | private ExecutorService executorService = Executors.newFixedThreadPool(5); 30 | 31 | // JDK可执行定时任务的线程池 32 | private ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5); 33 | 34 | // Spring普通线程池 35 | @Autowired 36 | private ThreadPoolTaskExecutor taskExecutor; 37 | 38 | // Spring可执行定时任务的线程池 39 | @Autowired 40 | private ThreadPoolTaskScheduler taskScheduler; 41 | 42 | @Autowired 43 | private AlphaService alphaService; 44 | 45 | private void sleep(long m) { 46 | try { 47 | Thread.sleep(m); 48 | } catch (InterruptedException e) { 49 | e.printStackTrace(); 50 | } 51 | } 52 | 53 | // 1.JDK普通线程池 54 | @Test 55 | public void testExecutorService() { 56 | Runnable task = new Runnable() { 57 | @Override 58 | public void run() { 59 | logger.debug("Hello ExecutorService"); 60 | } 61 | }; 62 | 63 | for (int i = 0; i < 10; i++) { 64 | executorService.submit(task); 65 | } 66 | 67 | sleep(10000); 68 | } 69 | 70 | // 2.JDK定时任务线程池 71 | @Test 72 | public void testScheduledExecutorService() { 73 | Runnable task = new Runnable() { 74 | @Override 75 | public void run() { 76 | logger.debug("Hello ScheduledExecutorService"); 77 | } 78 | }; 79 | 80 | //10s后开始执行,时间间隔是1s 81 | scheduledExecutorService.scheduleAtFixedRate(task, 10000, 1000, TimeUnit.MILLISECONDS); 82 | 83 | //阻塞30s,定时任务结束 84 | sleep(30000); 85 | } 86 | 87 | // 3.Spring普通线程池 88 | @Test 89 | public void testThreadPoolTaskExecutor() { 90 | Runnable task = new Runnable() { 91 | @Override 92 | public void run() { 93 | logger.debug("Hello ThreadPoolTaskExecutor"); 94 | } 95 | }; 96 | 97 | for (int i = 0; i < 10; i++) { 98 | taskExecutor.submit(task); 99 | } 100 | 101 | sleep(10000); 102 | } 103 | 104 | // 4.Spring定时任务线程池 105 | @Test 106 | public void testThreadPoolTaskScheduler() { 107 | Runnable task = new Runnable() { 108 | @Override 109 | public void run() { 110 | logger.debug("Hello ThreadPoolTaskScheduler"); 111 | } 112 | }; 113 | 114 | //当前的时间加10s,相当于延迟的作用 115 | Date startTime = new Date(System.currentTimeMillis() + 10000); 116 | taskScheduler.scheduleAtFixedRate(task, startTime, 1000); 117 | 118 | sleep(30000); 119 | } 120 | 121 | // 5.Spring普通线程池(简化) 122 | @Test 123 | public void testThreadPoolTaskExecutorSimple() { 124 | for (int i = 0; i < 10; i++) { 125 | alphaService.execute1(); 126 | } 127 | 128 | //让线程阻塞10s 129 | sleep(10000); 130 | } 131 | 132 | // 6.Spring定时任务线程池(简化)。程序只要启动,就会去调用execute2()方法 133 | @Test 134 | public void testThreadPoolTaskSchedulerSimple() { 135 | sleep(30000); 136 | } 137 | 138 | } 139 | -------------------------------------------------------------------------------- /src/test/java/com/community/TransactionTests.java: -------------------------------------------------------------------------------- 1 | package com.community; 2 | 3 | import com.community.service.AlphaService; 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 TransactionTests { 15 | 16 | @Autowired 17 | private AlphaService alphaService; 18 | 19 | @Test 20 | public void testSave1() { 21 | Object obj = alphaService.save1(); 22 | System.out.println(obj); 23 | } 24 | 25 | @Test 26 | public void testSave2() { 27 | Object obj = alphaService.save2(); 28 | System.out.println(obj); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/test/java/com/community/WkTests.java: -------------------------------------------------------------------------------- 1 | package com.community; 2 | 3 | import java.io.IOException; 4 | 5 | public class WkTests { 6 | 7 | public static void main(String[] args) { 8 | String cmd = "d:/work/wkhtmltopdf/bin/wkhtmltoimage --quality 75 https://www.nowcoder.com d:/work/data/wk-images/3.png"; 9 | try { 10 | Runtime.getRuntime().exec(cmd); 11 | System.out.println("ok."); 12 | } catch (IOException e) { 13 | e.printStackTrace(); 14 | } 15 | } 16 | 17 | } 18 | --------------------------------------------------------------------------------