map = new HashMap<>(16);
64 | // map.put("shareUrl", domain + contextPath + "/share/image/" + fileName);
65 | map.put("shareUrl", shareBucketUrl + "/" + fileName);
66 | return CommunityUtil.getJSONString(0, null, map);
67 | }
68 |
69 | /**
70 | * 废弃(改为从七牛云获取图片) 获取长图
71 | *
72 | * @param fileName
73 | * @param response
74 | */
75 | @GetMapping("/share/image/{fileName}")
76 | public void getShareImage(
77 | @PathVariable("fileName") String fileName, HttpServletResponse response) {
78 | if (StringUtils.isBlank(fileName)) {
79 | throw new IllegalArgumentException("文件名不能为空");
80 | }
81 | response.setContentType("image/png");
82 | File file = new File(wkImageStorage + "/" + fileName + ".png");
83 | try {
84 | OutputStream os = response.getOutputStream();
85 | FileInputStream fis = new FileInputStream(file);
86 | byte[] buffer = new byte[1024];
87 | int b = 0;
88 | while ((b = fis.read(buffer)) != -1) {
89 | os.write(buffer, 0, b);
90 | }
91 | } catch (IOException e) {
92 | LOGGER.error("获取长图失败: " + e.getMessage());
93 | }
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/src/main/java/tech/turl/community/controller/CommentController.java:
--------------------------------------------------------------------------------
1 | package tech.turl.community.controller;
2 |
3 | import org.springframework.beans.factory.annotation.Autowired;
4 | import org.springframework.data.redis.core.RedisTemplate;
5 | import org.springframework.stereotype.Controller;
6 | import org.springframework.web.bind.annotation.PathVariable;
7 | import org.springframework.web.bind.annotation.PostMapping;
8 | import org.springframework.web.bind.annotation.RequestMapping;
9 | import tech.turl.community.entity.Comment;
10 | import tech.turl.community.entity.DiscussPost;
11 | import tech.turl.community.entity.Event;
12 | import tech.turl.community.entity.User;
13 | import tech.turl.community.event.EventProducer;
14 | import tech.turl.community.service.CommentService;
15 | import tech.turl.community.service.DiscussPostService;
16 | import tech.turl.community.util.CommunityConstant;
17 | import tech.turl.community.util.HostHolder;
18 | import tech.turl.community.util.RedisKeyUtil;
19 |
20 | import java.util.Date;
21 |
22 | /**
23 | * @author zhengguohuang
24 | * @date 2021/03/19
25 | */
26 | @Controller
27 | @RequestMapping("/comment")
28 | public class CommentController implements CommunityConstant {
29 | @Autowired private CommentService commentService;
30 |
31 | @Autowired private HostHolder hostHolder;
32 |
33 | @Autowired private EventProducer eventProducer;
34 |
35 | @Autowired private DiscussPostService discussPostService;
36 |
37 | @Autowired private RedisTemplate redisTemplate;
38 |
39 | /**
40 | * 添加评论
41 | *
42 | * @param discussPostId
43 | * @param comment
44 | * @return
45 | */
46 | @PostMapping("/add/{discussPostId}")
47 | public String addComment(@PathVariable("discussPostId") int discussPostId, Comment comment) {
48 | User user = hostHolder.getUser();
49 | comment.setUserId(user.getId());
50 | comment.setStatus(0);
51 | comment.setCreateTime(new Date());
52 | commentService.addComment(comment);
53 |
54 | // 触发事件
55 | Event event =
56 | new Event()
57 | .setTopic(TOPIC_COMMENT)
58 | .setUserId(comment.getUserId())
59 | .setEntityType(comment.getEntityType())
60 | .setEntityId(comment.getEntityId())
61 | .setData("postId", discussPostId);
62 | if (comment.getEntityType() == ENTITY_TYPE_POST) {
63 | DiscussPost target = discussPostService.findDiscussPostById(comment.getEntityId());
64 | event.setEntityUserId(target.getUserId());
65 | } else if (comment.getEntityType() == ENTITY_TYPE_COMMENT) {
66 | Comment target = commentService.findCommentsById(comment.getEntityId());
67 | event.setEntityUserId(target.getUserId());
68 | }
69 | eventProducer.fireEvent(event);
70 |
71 | if (comment.getEntityType() == ENTITY_TYPE_POST) {
72 | // 触发发帖事件
73 | event =
74 | new Event()
75 | .setTopic(TOPIC_PUBLISH)
76 | .setUserId(comment.getUserId())
77 | .setEntityType(ENTITY_TYPE_POST)
78 | .setEntityId(discussPostId);
79 | eventProducer.fireEvent(event);
80 | // 计算帖子的分数
81 | String redisKey = RedisKeyUtil.getPostScoreKey();
82 | redisTemplate.opsForSet().add(redisKey, discussPostId);
83 | }
84 |
85 | return "redirect:/discuss/detail/" + discussPostId;
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/src/main/java/tech/turl/community/config/QuartzConfig.java:
--------------------------------------------------------------------------------
1 | package tech.turl.community.config;
2 |
3 | import org.quartz.JobDataMap;
4 | import org.quartz.JobDetail;
5 | import org.springframework.context.annotation.Bean;
6 | import org.springframework.context.annotation.Configuration;
7 | import org.springframework.scheduling.quartz.JobDetailFactoryBean;
8 | import org.springframework.scheduling.quartz.SimpleTriggerFactoryBean;
9 | import tech.turl.community.quartz.AlphaJob;
10 | import tech.turl.community.quartz.PostScoreRefreshJob;
11 |
12 | /**
13 | * @author zhengguohuang
14 | * @date 2021/03/29
15 | */
16 | @Configuration
17 | public class QuartzConfig {
18 |
19 | /**
20 | * 配置JobDetail
21 | *
22 | *
23 | *
24 | *
25 | *
26 | *
27 | *
28 | *
FactoryBean可以简化Bean的实例化过程:
29 | *
30 | *
31 | *
32 | *
33 | *
34 | *
35 | *
36 | *
1.通过FactoryBean封装Bean的实例化过程。
37 | *
38 | *
39 | *
40 | *
41 | *
42 | *
43 | *
44 | *
2.将FactoryBean装配到Spring容器里。
45 | *
46 | *
47 | *
48 | *
49 | *
50 | *
51 | *
52 | *
3.将FactoryBean注入给其他的Bean。
53 | *
54 | *
55 | *
56 | *
57 | *
58 | *
59 | *
60 | *
4.该Bean得到的是FactoryBean所管理的对象实例。
61 | *
62 | * @return
63 | */
64 | // @Bean
65 | public JobDetailFactoryBean alphaJobDetail() {
66 | JobDetailFactoryBean factoryBean = new JobDetailFactoryBean();
67 | factoryBean.setJobClass(AlphaJob.class);
68 | factoryBean.setName("alphaJob");
69 | factoryBean.setGroup("alphaJobGroup");
70 | factoryBean.setDurability(true);
71 | factoryBean.setRequestsRecovery(true);
72 | return factoryBean;
73 | }
74 |
75 | /**
76 | * 配置Trigger(SimpleTriggerFactoryBean, CronTriggerFactoryBean)
77 | *
78 | * @param alphaJobDetail
79 | * @return
80 | */
81 | // @Bean
82 | public SimpleTriggerFactoryBean alphaTrigger(JobDetail alphaJobDetail) {
83 | SimpleTriggerFactoryBean factoryBean = new SimpleTriggerFactoryBean();
84 | factoryBean.setJobDetail(alphaJobDetail);
85 | factoryBean.setName("alphaTrigger");
86 | factoryBean.setGroup("alphaTriggerGroup");
87 | factoryBean.setRepeatInterval(3000);
88 | factoryBean.setJobDataMap(new JobDataMap());
89 |
90 | return factoryBean;
91 | }
92 |
93 | /**
94 | * 刷新帖子分数任务
95 | *
96 | * @return
97 | */
98 | @Bean
99 | public JobDetailFactoryBean postScoreRefreshJobDetail() {
100 | JobDetailFactoryBean factoryBean = new JobDetailFactoryBean();
101 | factoryBean.setJobClass(PostScoreRefreshJob.class);
102 | factoryBean.setName("postScoreRefreshJob");
103 | factoryBean.setGroup("communityJobGroup");
104 | factoryBean.setDurability(true);
105 | factoryBean.setRequestsRecovery(true);
106 | return factoryBean;
107 | }
108 |
109 | /**
110 | * @param postScoreRefreshJobDetail
111 | * @return
112 | */
113 | @Bean
114 | public SimpleTriggerFactoryBean postScoreRefreshTrigger(JobDetail postScoreRefreshJobDetail) {
115 | SimpleTriggerFactoryBean factoryBean = new SimpleTriggerFactoryBean();
116 | factoryBean.setJobDetail(postScoreRefreshJobDetail);
117 | factoryBean.setName("postScoreRefreshTrigger");
118 | factoryBean.setGroup("communityTriggerGroup");
119 | factoryBean.setRepeatInterval(1000 * 60 * 5);
120 | factoryBean.setJobDataMap(new JobDataMap());
121 | return factoryBean;
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/src/main/java/tech/turl/community/service/DataService.java:
--------------------------------------------------------------------------------
1 | package tech.turl.community.service;
2 |
3 | import org.springframework.beans.factory.annotation.Autowired;
4 | import org.springframework.data.redis.connection.RedisStringCommands;
5 | import org.springframework.data.redis.core.RedisCallback;
6 | import org.springframework.data.redis.core.RedisTemplate;
7 | import org.springframework.stereotype.Service;
8 | import tech.turl.community.util.RedisKeyUtil;
9 |
10 | import java.text.SimpleDateFormat;
11 | import java.util.ArrayList;
12 | import java.util.Calendar;
13 | import java.util.Date;
14 | import java.util.List;
15 |
16 | /**
17 | * @author zhengguohuang
18 | * @date 2021/03/29
19 | */
20 | @Service
21 | public class DataService {
22 | @Autowired private RedisTemplate redisTemplate;
23 | private SimpleDateFormat df = new SimpleDateFormat("yyyyMMdd");
24 |
25 | /**
26 | * 统计UV,将指定ip计入UV
27 | *
28 | * @param ip
29 | */
30 | public void recordUV(String ip) {
31 | String redisKey = RedisKeyUtil.getUvKey(df.format(new Date()));
32 | redisTemplate.opsForHyperLogLog().add(redisKey, ip);
33 | }
34 |
35 | /**
36 | * 统计指定日期范围内的UV
37 | *
38 | * @param startDate
39 | * @param endDate
40 | * @return
41 | */
42 | public long calculateUV(Date startDate, Date endDate) {
43 | if (startDate == null || endDate == null) {
44 | throw new IllegalArgumentException("参数不能为空");
45 | }
46 | // 整理该日期范围内的Key
47 | List keyList = new ArrayList<>();
48 | Calendar calendar = Calendar.getInstance();
49 | calendar.setTime(startDate);
50 | while (!calendar.getTime().after(endDate)) {
51 | String key = RedisKeyUtil.getUvKey(df.format(calendar.getTime()));
52 | keyList.add(key);
53 | calendar.add(Calendar.DATE, 1);
54 | }
55 | // 合并这些数据
56 | String redisKey = RedisKeyUtil.getUvKey(df.format(startDate), df.format(endDate));
57 | redisTemplate.opsForHyperLogLog().union(redisKey, keyList.toArray());
58 | // 统计数据
59 | return redisTemplate.opsForHyperLogLog().size(redisKey);
60 | }
61 |
62 | /**
63 | * 统计DAU,将指定用户计入DAU
64 | *
65 | * @param
66 | */
67 | public void recordDAU(int userId) {
68 | String redisKey = RedisKeyUtil.getDauKey(df.format(new Date()));
69 | redisTemplate.opsForValue().setBit(redisKey, userId, true);
70 | }
71 |
72 | /**
73 | * 统计指定日期范围内的DAU
74 | *
75 | * @param startDate
76 | * @param endDate
77 | * @return
78 | */
79 | public long calculateDAU(Date startDate, Date endDate) {
80 | // 整理该日期范围内的Key
81 | List keyList = new ArrayList<>();
82 | Calendar calendar = Calendar.getInstance();
83 | calendar.setTime(startDate);
84 | while (!calendar.getTime().after(endDate)) {
85 | String key = RedisKeyUtil.getDauKey(df.format(calendar.getTime()));
86 | keyList.add(key.getBytes());
87 | calendar.add(Calendar.DATE, 1);
88 | }
89 | // 合并这些数据
90 | String redisKey = RedisKeyUtil.getDauKey(df.format(startDate), df.format(endDate));
91 | return (long)
92 | redisTemplate.execute(
93 | (RedisCallback)
94 | redisConnection -> {
95 | redisConnection.bitOp(
96 | RedisStringCommands.BitOperation.OR,
97 | redisKey.getBytes(),
98 | keyList.toArray(new byte[0][0]));
99 | return redisConnection.bitCount(redisKey.getBytes());
100 | });
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/src/test/java/tech/turl/community/ThreadPoolTests.java:
--------------------------------------------------------------------------------
1 | package tech.turl.community;
2 |
3 | import org.junit.jupiter.api.Test;
4 | import org.slf4j.Logger;
5 | import org.slf4j.LoggerFactory;
6 | import org.springframework.beans.factory.annotation.Autowired;
7 | import org.springframework.boot.test.context.SpringBootTest;
8 | import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
9 | import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
10 | import org.springframework.test.context.ContextConfiguration;
11 | import tech.turl.community.service.AlphaService;
12 |
13 | import java.util.Date;
14 | import java.util.concurrent.ExecutorService;
15 | import java.util.concurrent.Executors;
16 | import java.util.concurrent.ScheduledExecutorService;
17 | import java.util.concurrent.TimeUnit;
18 |
19 | /**
20 | * @author zhengguohuang
21 | * @date 2021/03/29
22 | */
23 | @SpringBootTest
24 | @ContextConfiguration(classes = CommunityApplication.class)
25 | public class ThreadPoolTests {
26 | private static final Logger LOGGER = LoggerFactory.getLogger(ThreadPoolTests.class);
27 | // 1.JDK普通线程
28 | private ExecutorService executorService = Executors.newFixedThreadPool(5);
29 |
30 | // 2.JDK可执行定时任务的线程池
31 | private ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);
32 |
33 | // 3.Spring普通线程池
34 | @Autowired private ThreadPoolTaskExecutor taskExecutor;
35 |
36 | // 4.Spring可执行定时任务的线程池
37 | @Autowired private ThreadPoolTaskScheduler taskScheduler;
38 |
39 | @Autowired private AlphaService alphaService;
40 |
41 | /**
42 | * 封装的休眠函数
43 | *
44 | * @param m
45 | */
46 | private void sleep(long m) {
47 | try {
48 | Thread.sleep(m);
49 | } catch (InterruptedException e) {
50 | e.printStackTrace();
51 | }
52 | }
53 |
54 | /** 1.JDK普通线程池执行任务 */
55 | @Test
56 | public void testExecutorService() {
57 | Worker worker = new Worker("ExecutorService");
58 | for (int i = 0; i < 10; ++i) {
59 | executorService.execute(worker);
60 | }
61 | sleep(10000);
62 | }
63 |
64 | /** 2.JDK定时任务线程池 */
65 | @Test
66 | public void testScheduledExecutorService() {
67 | Worker worker = new Worker("ScheduledExecutorService");
68 | scheduledExecutorService.scheduleAtFixedRate(worker, 10000, 1000, TimeUnit.MILLISECONDS);
69 | sleep(30000);
70 | }
71 |
72 | /** 3.Spring普通线程池 */
73 | @Test
74 | public void testThreadPoolTaskExecutor() {
75 | Worker worker = new Worker("ThreadPoolTaskExecutor");
76 | for (int i = 0; i < 10; i++) {
77 | taskExecutor.submit(worker);
78 | }
79 | sleep(10000);
80 | }
81 |
82 | /** 4.Spring定时线程池 */
83 | @Test
84 | public void testThreadPoolTaskScheduler() {
85 | Worker worker = new Worker("ThreadPoolTaskScheduler");
86 | Date startTime = new Date(System.currentTimeMillis() + 10_000);
87 | taskScheduler.scheduleAtFixedRate(worker, startTime, 1_000);
88 | sleep(30_000);
89 | }
90 |
91 | /** 5.Spring普通线程池,简单版本 */
92 | @Test
93 | public void testThreadPoolTaskExecutorSimple() {
94 | for (int i = 0; i < 10; i++) {
95 | alphaService.execute1();
96 | }
97 | sleep(10_000);
98 | }
99 |
100 | /** 6.Spring定时任务线程池(简化) */
101 | @Test
102 | public void testThreadPoolTaskSchedulerSimple() {
103 | sleep(30_000);
104 | }
105 |
106 | class Worker implements Runnable {
107 | private String log;
108 |
109 | public Worker(String log) {
110 | this.log = log;
111 | }
112 |
113 | @Override
114 | public void run() {
115 | LOGGER.debug("hello " + log);
116 | }
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/src/main/resources/mapper/message-mapper.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
20 |
29 |
30 |
39 |
46 |
56 |
57 | INSERT INTO message (from_id, to_id, conversation_id, content, status, create_time)
58 | VALUES (#{fromId},#{toId},#{conversationId},#{content},#{status},#{createTime})
59 |
60 |
61 |
62 | UPDATE message SET status = #{status}
63 | WHERE id IN
64 |
65 | #{id}
66 |
67 |
68 |
69 |
80 |
87 |
96 |
106 |
107 |
--------------------------------------------------------------------------------
/src/main/java/tech/turl/community/service/ElasticsearchService.java:
--------------------------------------------------------------------------------
1 | package tech.turl.community.service;
2 |
3 | import org.elasticsearch.index.query.QueryBuilders;
4 | import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
5 | import org.elasticsearch.search.sort.SortBuilders;
6 | import org.elasticsearch.search.sort.SortOrder;
7 | import org.springframework.beans.BeanUtils;
8 | import org.springframework.beans.factory.annotation.Autowired;
9 | import org.springframework.data.domain.Page;
10 | import org.springframework.data.domain.PageImpl;
11 | import org.springframework.data.domain.PageRequest;
12 | import org.springframework.data.domain.Pageable;
13 | import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
14 | import org.springframework.data.elasticsearch.core.SearchHit;
15 | import org.springframework.data.elasticsearch.core.SearchHits;
16 | import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
17 | import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
18 | import org.springframework.stereotype.Service;
19 | import tech.turl.community.dao.elasticsearch.DiscussPostRepository;
20 | import tech.turl.community.entity.DiscussPost;
21 |
22 | import java.util.ArrayList;
23 | import java.util.List;
24 |
25 | /**
26 | * @author zhengguohuang
27 | * @date 2021/03/26
28 | */
29 | @Service
30 | public class ElasticsearchService {
31 | @Autowired private DiscussPostRepository discussPostRepository;
32 |
33 | @Autowired private ElasticsearchRestTemplate elasticsearchRestTemplate;
34 |
35 | /**
36 | * 插入或修改帖子
37 | *
38 | * @param post
39 | */
40 | public void saveDiscussPost(DiscussPost post) {
41 | discussPostRepository.save(post);
42 | }
43 |
44 | /**
45 | * 删除帖子
46 | *
47 | * @param id
48 | */
49 | public void deleteDiscussPost(int id) {
50 | discussPostRepository.deleteById(id);
51 | }
52 |
53 | /**
54 | * 搜索并高亮分页显示
55 | *
56 | * @param keyword
57 | * @param current
58 | * @param limit
59 | * @return
60 | */
61 | public Page searchDiscussPost(String keyword, int current, int limit) {
62 | Pageable pageable = PageRequest.of(current, limit);
63 | NativeSearchQuery searchQuery =
64 | new NativeSearchQueryBuilder()
65 | .withQuery(QueryBuilders.multiMatchQuery(keyword, "title", "content"))
66 | .withSort(SortBuilders.fieldSort("type").order(SortOrder.DESC))
67 | .withSort(SortBuilders.fieldSort("score").order(SortOrder.DESC))
68 | .withSort(SortBuilders.fieldSort("createTime").order(SortOrder.DESC))
69 | .withPageable(pageable)
70 | .withHighlightFields(
71 | new HighlightBuilder.Field("title")
72 | .preTags("")
73 | .postTags(""),
74 | new HighlightBuilder.Field("content")
75 | .preTags("")
76 | .postTags(""))
77 | .build();
78 |
79 | SearchHits searchHits =
80 | elasticsearchRestTemplate.search(searchQuery, DiscussPost.class);
81 | if (searchHits.getTotalHits() <= 0) {
82 | return null;
83 | }
84 | List list = new ArrayList<>();
85 | for (SearchHit hit : searchHits) {
86 | DiscussPost content = hit.getContent();
87 | DiscussPost post = new DiscussPost();
88 | BeanUtils.copyProperties(content, post);
89 | List list1 = hit.getHighlightFields().get("title");
90 | if (list1 != null) {
91 | post.setTitle(list1.get(0));
92 | }
93 | List list2 = hit.getHighlightFields().get("content");
94 | if (list2 != null) {
95 | post.setContent(list2.get(0));
96 | }
97 | list.add(post);
98 | }
99 | return new PageImpl<>(list, pageable, searchHits.getTotalHits());
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/src/main/java/tech/turl/community/util/RedisKeyUtil.java:
--------------------------------------------------------------------------------
1 | package tech.turl.community.util;
2 |
3 | /**
4 | * @author zhengguohuang
5 | * @date 2021/03/21
6 | */
7 | public class RedisKeyUtil {
8 | private static final String SPLIT = ":";
9 | private static final String PREFIX_ENTITY_LIKE = "like:entity";
10 | private static final String PREFIX_USER_LIKE = "like:user";
11 | private static final String PREFIX_FOLLOWEE = "followee";
12 | private static final String PREFIX_FOLLOWER = "follower";
13 | private static final String PREFIX_KAPTCHA = "kaptcha";
14 | private static final String PREFIX_TICKET = "ticket";
15 | private static final String PREFIX_USER = "user";
16 | private static final String PREFIX_UV = "uv";
17 | private static final String PREFIX_DAU = "dau";
18 | private static final String PREFIX_POST = "post";
19 | private static final String PREFIX_RATE_LIMIT_PUBLISH = "rate:limit:publish";
20 |
21 | /**
22 | * 某个实体的赞 like:entity:entityType:entityId->set(userId)
23 | *
24 | * @param entityType
25 | * @param entityId
26 | * @return
27 | */
28 | public static String getEntityLikeKey(int entityType, int entityId) {
29 | return PREFIX_ENTITY_LIKE + SPLIT + entityType + SPLIT + entityId;
30 | }
31 |
32 | /**
33 | * 某个用户的赞 like:user:userId -> int
34 | *
35 | * @param userId
36 | * @return
37 | */
38 | public static String getUserLikeKey(int userId) {
39 | return PREFIX_USER_LIKE + SPLIT + userId;
40 | }
41 |
42 | /**
43 | * 某个用户关注的实体 followee:userId:entityType -> zset(entityId, now)
44 | *
45 | * @param userId
46 | * @param entityType
47 | * @return
48 | */
49 | public static String getFolloweeKey(int userId, int entityType) {
50 | return PREFIX_FOLLOWEE + SPLIT + userId + SPLIT + entityType;
51 | }
52 |
53 | /**
54 | * 某个实体拥有的粉丝 follower:entityType:entityId -> zset(userId, now)
55 | *
56 | * @param entityType
57 | * @param entityId
58 | * @return
59 | */
60 | public static String getFollowerKey(int entityType, int entityId) {
61 | return PREFIX_FOLLOWER + SPLIT + entityType + SPLIT + entityId;
62 | }
63 |
64 | /**
65 | * 登录验证码
66 | *
67 | * @param owner 服务器发给客户端的验证码
68 | * @return
69 | */
70 | public static String getKaptchaKey(String owner) {
71 | return PREFIX_KAPTCHA + SPLIT + owner;
72 | }
73 |
74 | /**
75 | * 登录的凭证
76 | *
77 | * @param ticket
78 | * @return
79 | */
80 | public static String getTicketKey(String ticket) {
81 | return PREFIX_TICKET + SPLIT + ticket;
82 | }
83 |
84 | /**
85 | * 用户
86 | *
87 | * @param userId
88 | * @return
89 | */
90 | public static String getUserKey(int userId) {
91 | return PREFIX_USER + SPLIT + userId;
92 | }
93 |
94 | /**
95 | * 单日UV
96 | *
97 | * @param date
98 | * @return
99 | */
100 | public static String getUvKey(String date) {
101 | return PREFIX_UV + SPLIT + date;
102 | }
103 |
104 | /**
105 | * 区间UV
106 | *
107 | * @param startDate
108 | * @param endDate
109 | * @return
110 | */
111 | public static String getUvKey(String startDate, String endDate) {
112 | return PREFIX_UV + SPLIT + startDate + SPLIT + endDate;
113 | }
114 |
115 | /**
116 | * 单日DAU
117 | *
118 | * @param date
119 | * @return
120 | */
121 | public static String getDauKey(String date) {
122 | return PREFIX_DAU + SPLIT + date;
123 | }
124 |
125 | /**
126 | * 区间DAU
127 | *
128 | * @param startDate
129 | * @param endDate
130 | * @return
131 | */
132 | public static String getDauKey(String startDate, String endDate) {
133 | return PREFIX_DAU + SPLIT + startDate + SPLIT + endDate;
134 | }
135 |
136 | /**
137 | * 帖子分数
138 | *
139 | * @return
140 | */
141 | public static String getPostScoreKey() {
142 | return PREFIX_POST + SPLIT + "score";
143 | }
144 |
145 | /**
146 | * 发帖限流
147 | *
148 | * @param userId
149 | * @return
150 | */
151 | public static String getRateLimitKey(int userId) {
152 | return PREFIX_RATE_LIMIT_PUBLISH + SPLIT + userId;
153 | }
154 | }
155 |
--------------------------------------------------------------------------------
/src/main/resources/logback-spring.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | community
4 |
5 |
6 |
7 |
8 |
9 |
10 | ${LOG_PATH}/${APPDIR}/log_error.log
11 |
12 | ${LOG_PATH}/${APPDIR}/error/log-error-%d{yyyy-MM-dd}.%i.log
13 |
14 | 5MB
15 |
16 | 30
17 |
18 | true
19 |
20 | %d %level [%thread] %logger{10} [%file:%line] %msg%n
21 | utf-8
22 |
23 |
24 | error
25 | ACCEPT
26 | DENY
27 |
28 |
29 |
30 |
31 |
32 | ${LOG_PATH}/${APPDIR}/log_warn.log
33 |
34 | ${LOG_PATH}/${APPDIR}/warn/log-warn-%d{yyyy-MM-dd}.%i.log
35 |
36 | 5MB
37 |
38 | 30
39 |
40 | true
41 |
42 | %d %level [%thread] %logger{10} [%file:%line] %msg%n
43 | utf-8
44 |
45 |
46 | warn
47 | ACCEPT
48 | DENY
49 |
50 |
51 |
52 |
53 |
54 | ${LOG_PATH}/${APPDIR}/log_info.log
55 |
56 | ${LOG_PATH}/${APPDIR}/info/log-info-%d{yyyy-MM-dd}.%i.log
57 |
58 | 5MB
59 |
60 | 30
61 |
62 | true
63 |
64 | %d %level [%thread] %logger{10} [%file:%line] %msg%n
65 | utf-8
66 |
67 |
68 | info
69 | ACCEPT
70 | DENY
71 |
72 |
73 |
74 |
75 |
76 |
77 | %d %level [%thread] %logger{10} [%file:%line] %msg%n
78 | utf-8
79 |
80 |
81 | debug
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
--------------------------------------------------------------------------------
/src/main/java/tech/turl/community/service/AlphaService.java:
--------------------------------------------------------------------------------
1 | package tech.turl.community.service;
2 |
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 | import org.springframework.beans.factory.annotation.Autowired;
6 | import org.springframework.scheduling.annotation.Async;
7 | import org.springframework.stereotype.Service;
8 | import org.springframework.transaction.TransactionDefinition;
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.transaction.support.TransactionCallback;
13 | import org.springframework.transaction.support.TransactionTemplate;
14 | import tech.turl.community.dao.AlphaDao;
15 | import tech.turl.community.dao.DiscussPostMapper;
16 | import tech.turl.community.dao.UserMapper;
17 | import tech.turl.community.entity.DiscussPost;
18 | import tech.turl.community.entity.User;
19 | import tech.turl.community.util.CommunityUtil;
20 |
21 | import javax.annotation.PostConstruct;
22 | import javax.annotation.PreDestroy;
23 | import java.util.Date;
24 |
25 | /**
26 | * @author zhengguohuang
27 | * @date 2021/03/17
28 | */
29 | @Service
30 | public class AlphaService {
31 |
32 | @Autowired private AlphaDao alphaDao;
33 |
34 | @Autowired private UserMapper userMapper;
35 |
36 | @Autowired DiscussPostMapper discussPostMapper;
37 |
38 | private static final Logger LOGGER = LoggerFactory.getLogger(AlphaService.class);
39 |
40 | public AlphaService() {
41 | System.out.println("实例化AlphaService");
42 | }
43 |
44 | @PostConstruct
45 | public void init() {
46 | System.out.println("AlphaService init().");
47 | }
48 |
49 | @PreDestroy
50 | public void destory() {
51 | System.out.println("销毁AlphaService");
52 | }
53 |
54 | public String find() {
55 | return alphaDao.select();
56 | }
57 |
58 | /**
59 | * REQUIRED:支持当前事务(外部事务),如果不存在则创建新事务 REQUIRES_NEW:创建一个新事务,并且暂停当前事务(外部事务) NESTED:
60 | * 如果当前存在事务(外部事务)则嵌套在该事务中执行(独立的提交和回滚),否则就和REQUIRED一样
61 | *
62 | * @return
63 | */
64 | @Transactional(
65 | isolation = Isolation.READ_COMMITTED,
66 | propagation = Propagation.REQUIRED,
67 | rollbackFor = Exception.class)
68 | public Object save1() {
69 | // 新增用户
70 | User user = new User();
71 | user.setUsername("alpha");
72 | user.setSalt(CommunityUtil.generateUUID().substring(0, 5));
73 | user.setPassword(CommunityUtil.md5("123" + user.getSalt()));
74 | user.setEmail("alpha@qq.com");
75 | user.setHeaderUrl("http://image.nowcoder.com/head/99t.png");
76 | user.setCreateTime(new Date());
77 | userMapper.insertUser(user);
78 |
79 | // 新增帖子
80 | DiscussPost post = new DiscussPost();
81 | post.setUserId(user.getId());
82 | post.setTitle("hello");
83 | post.setContent("新人报道!");
84 | post.setCreateTime(new Date());
85 | discussPostMapper.insertDiscussPost(post);
86 |
87 | Integer.valueOf("abc");
88 | return "ok";
89 | }
90 |
91 | @Autowired private TransactionTemplate transactionTemplate;
92 |
93 | public Object save2() {
94 | transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
95 | transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
96 | return transactionTemplate.execute(
97 | (TransactionCallback