Project Overview
This implementation covers the extraction of common components into a shared module, the design and execution of social circle features, and the integration of various technical solutions.
Component Extraction
Common elements across projects should be centralized into a shared engineering module for reuse. This section outlines how to extract common objects from SSO and server modules.
Creating a Shared Module
A new Maven project my-tanhua-common was established with dependencies including Lombok, MyBatis-Plus, and Jackson.
<project xmlns="http://maven.apache.org/POM/4.0.0">
<artifactId>my-tanhua-common</artifactId>
<dependencies>
<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency>
<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId></dependency>
<dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId></dependency>
</dependencies>
</project>
Universal Enumerations
The SexEnum was moved to the common module. Configuration updates were made in application.properties:
mybatis-plus.type-enums-package=com.tanhua.common.enums
Mapper Extraction
User-related mappers were extracted to com.tanhua.common.mapper:
public interface UserMapper extends BaseMapper<User> {}
public interface UserInfoMapper extends BaseMapper<UserInfo> {}
POJO and Utility Extraction
Base classes, user models, and utility functions were also relocated to the common module.
Social Circle Features
The social circle functionality includes publishing posts, viewing friend feeds, browsing recommended content, liking, commenting, and favoriting.
Implementation Strategy
Key considerations for data management:
- High volume growth with user base expansion
- Read-heavy operations compared to writes
- Privacy restrictions on post visibility
These factors led to using MongoDB for storage and optimizing queries for performance.
Database Schema
Four core collections are defined:
Publish Collection
{
"_id": "5fae53d17e52992e78a3db61",
"pid": 1001,
"userId": 1,
"text": "Today's mood is great",
"medias": "http://xxxx/x/y/z.jpg",
"seeType": 1,
"seeList": [1,2,3],
"notSeeList": [4,5,6],
"longitude": "108.840974298098",
"latitude": "34.2789316522934",
"locationName": "Shanghai Pudong District",
"created": 1568012791171
}
Album Collection
{
"_id": "5fae539d7e52992e78a3b684",
"publishId": "5fae53d17e52992e78a3db61",
"created": 1568012791171
}
Timeline Collection
{
"_id": "5fae539b7e52992e78a3b4ae",
"userId": 2,
"publishId": "5fae53d17e52992e78a3db61",
"date": 1568012791171
}
Comments Collection
{
"_id": "5fae539d7e52992e78a3b648",
"publishId": "5fae53d17e52992e78a3db61",
"commentType": 1,
"content": "Great!",
"userId": 2,
"publishUserId": 9,
"isParent": false,
"parentId": 1001,
"created": 1568012791171
}
Friend Relationship Data
Friendship data structures are defined in MongoDB mock data:
@Data
@NoArgsConstructor
@AllArgsConstructor
@Document(collection = "tanhua_users")
public class Users implements Serializable {
private ObjectId id;
private Long userId;
private Long friendId;
private Long date;
}
Each user has 10 mock friends.
Viewing Friend Posts
Implementation involves querying timeline collections:
@Service(version = "1.0.0")
class QuanZiApiImpl implements QuanZiApi {
@Override
public PageInfo<Publish> queryPublishList(Long userId, Integer page, Integer pageSize) {
Pageable pageable = PageRequest.of(page - 1, pageSize, Sort.by(Sort.Order.desc("date")));
Query query = new Query().with(pageable);
List<TimeLine> timeLineList = mongoTemplate.find(query, TimeLine.class, "quanzi_time_line_" + userId);
List<Object> ids = CollUtil.getFieldValues(timeLineList, "publishId");
Query queryPublish = Query.query(Criteria.where("id").in(ids)).with(Sort.by(Sort.Order.desc("created")));
List<Publish> publishList = mongoTemplate.find(queryPublish, Publish.class);
pageInfo.setRecords(publishList);
return pageInfo;
}
}
Token Validation
A thread-local mechanism manages user sessions across requests:
public class UserThreadLocal {
private static final ThreadLocal<User> LOCAL = new ThreadLocal<>();
public static void set(User user) { LOCAL.set(user); }
public static User get() { return LOCAL.get(); }
public static void remove() { LOCAL.remove(); }
}
An interceptor validates tokens before processing requests:
@Component
public class UserTokenInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
String token = request.getHeader("Authorization");
if (StrUtil.isNotEmpty(token)) {
User user = userService.queryUserByToken(token);
if (user != null) {
UserThreadLocal.set(user);
return true;
}
}
response.setStatus(401);
return false;
}
}
Publishing Posts
Post creation involves multiple steps:
- Save to publish collection
- Add to user's album
- Asynchronously update friends' timelines
public String savePublish(Publish publish) {
publish.setId(ObjectId.get());
publish.setPid(idService.createId(IdType.PUBLISH));
publish.setCreated(System.currentTimeMillis());
mongoTemplate.save(publish);
Album album = new Album();
album.setId(ObjectId.get());
album.setCreated(System.currentTimeMillis());
album.setPublishId(publish.getId());
mongoTemplate.save(album, "quanzi_album_" + publish.getUserId());
timeLineService.saveTimeLine(publish.getUserId(), publish.getId());
return publish.getId().toHexString();
}
Recommended Posts
Recommended content is retrieved from Redis:
public PageInfo<Publish> queryRecommendPublishList(Long userId, Integer page, Integer pageSize) {
String key = "QUANZI_PUBLISH_RECOMMEND_" + userId;
String data = redisTemplate.opsForValue().get(key);
List<String> pids = StrUtil.split(data, ',');
int[] startEnd = PageUtil.transToStartEnd(page - 1, pageSize);
List<Long> pidLongList = new ArrayList<>();
for (int i = startEnd[0]; i < Math.min(startEnd[1], pids.size()); i++) {
pidLongList.add(Long.valueOf(pids.get(i)));
}
Query query = Query.query(Criteria.where("pid").in(pidLongList))
.with(Sort.by(Sort.Order.desc("created")));
List<Publish> publishList = mongoTemplate.find(query, Publish.class);
pageInfo.setRecords(publishList);
return pageInfo;
}
API Integration
The application layer handles REST endpoints:
@RestController
@RequestMapping("movements")
public class QuanZiController {
@GetMapping
public PageResult queryPublishList(
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer pageSize,
@RequestHeader("Authorization") String token) {
return quanZiService.queryPublishList(page, pageSize, token);
}
}
Data mapping uses Vo objects:
@Data
public class QuanZiVo {
private String id;
private Long userId;
private String avatar;
private String nickname;
private String gender;
private Integer age;
private String[] tags;
private String textContent;
private String[] imageContent;
private String distance;
private String createDate;
private Integer likeCount;
private Integer commentCount;
private Integer loveCount;
private Integer hasLiked;
private Integer hasLoved;
}
The service layer coordinates between Dubbo APIs and data retrieval:
public PageResult queryPublishList(Integer page, Integer pageSize, String token) {
User user = userService.queryUserByToken(token);
PageInfo<Publish> pageInfo = quanZiApi.queryPublishList(user.getId(), page, pageSize);
List<Publish> records = pageInfo.getRecords();
List<QuanZiVo> quanZiVoList = new ArrayList<>();
records.forEach(publish -> {
QuanZiVo vo = new QuanZiVo();
vo.setId(publish.getId().toHexString());
vo.setTextContent(publish.getText());
vo.setImageContent(publish.getMedias().toArray(new String[]{}));
vo.setUserId(publish.getUserId());
vo.setCreateDate(RelativeDateFormat.format(new Date(publish.getCreated())));
quanZiVoList.add(vo);
});
// Fetch user info and populate vo fields
return pageResult;
}