Fading Coder

One Final Commit for the Last Sprint

Home > Tools > Content

Mobile Application User Behavior Tracking and Interaction State Management

Tools 1

Event Tracking and Behavior Data Collection

User interaction data encompasses actions such as following authors, liking articles, marking as disliked, bookmarking content, and recording reading sessions. While these operations do not directly block core feature execution, capturing them is essential for downstream analytics and recommendation engines. The practice of embedding data collection hooks into user interactions is known as event tracking (or instrumentation), which captures specific user events—like icon taps, reading duration, or video watch time—and transmits them for processing.

This section details the implementation of follow, like, and read behavior persistence. Dislike and bookmark operations follow analogous patterns and can be derived from the provided examples.

Behavior Microservice Setup

Module Initialization

Given the high volume of interaction writes, a dedicated microservice handles all behavior-related persistence. Create a new module techpress-event-tracker with Maven dependencies mirroring the article service module, along with a corresponding Spring Boot application entry point.

Configuration

server:
  port: 9005
spring:
  application:
    name: event-tracker-service
  cloud:
    nacos:
      discovery:
        server-addr: 192.168.200.130:8848
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/techpress_behavior?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
    username: root
    password: root
mybatis-plus:
  mapper-locations: classpath*:mapper/*.xml
  type-aliases-package: com.techpress.model.event.pojos

Incorporate shared configurations from other services: global exception handling and Jackson serialization settings.

Follow Behavior Implementation

Requirements

When a user taps the follow button on an article detail page, the interaction must be persisted. The stored data will later feed into real-time stream processing for trending content computation. Unfollowing does not generate a behavior record—only the initial follow action is captured.

Data Model

The user_follow_action table stores follow events:

@Data
@TableName("user_follow_action")
public class UserFollowAction implements Serializable {
    private static final long serialVersionUID = 1L;

    @TableId(value = "id", type = IdType.ID_WORKER)
    private Long id;

    @TableField("actor_id")
    private Integer actorId;

    @TableField("content_id")
    private Long contentId;

    @TableField("target_user_id")
    private Integer targetUserId;

    @TableField("action_timestamp")
    private Date actionTimestamp;
}

The interaction_actor table represents the behavior entity—either a registered user or an anonymous device. It carries a category field: 0 for device, 1 for user. Each follow record references an actor, establishing a one-to-many relationship.

@Data
@TableName("interaction_actor")
public class InteractionActor implements Serializable {
    private static final long serialVersionUID = 1L;

    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;

    @TableField("category")
    private Short category;

    @TableField("reference_id")
    private Integer referenceId;

    @TableField("registered_at")
    private Date registeredAt;

    public enum Category {
        USER((short)1), DEVICE((short)0);
        private final short code;
        Category(short code) { this.code = code; }
        public short getCode() { return code; }
    }

    public boolean isUserType() {
        return category != null && category == Category.USER.getCode();
    }
}

Implementation Flow

The user service publishes a Kafka message upon follow. The behavior service consumes it, resolves the interaction actor, and persists the follow record.

Step 1: Publish from User Service

Add Kafka producer configuration to the user service:

spring:
  kafka:
    bootstrap-servers: 192.168.200.130:9092
    producer:
      key-serializer: org.apache.kafka.common.serialization.StringSerializer
      value-serializer: org.apache.kafka.common.serialization.StringSerializer

Define the transfer object:

@Data
public class FollowActionPayload {
    private Long contentId;
    private Integer targetUserId;
    private Integer userId;
}

Define the topic constant:

public class EventTopics {
    public static final String FOLLOW_ACTION = "user.follow.action";
}

Inject KafkaTemplate into the user relation service and dispatch the message:

FollowActionPayload payload = new FollowActionPayload();
payload.setTargetUserId(followId);
payload.setContentId(contentId);
payload.setUserId(currentUser.getId());
kafkaTemplate.send(EventTopics.FOLLOW_ACTION, JSON.toJSONString(payload));

Step 2: Resolve Interaction Actor in Behavior Service

@Mapper
public interface InteractionActorMapper extends BaseMapper<InteractionActor> {}

public interface InteractionActorService extends IService<InteractionActor> {
    InteractionActor resolveByUserOrDevice(Integer userId, Integer deviceId);
}

@Service
public class InteractionActorServiceImpl
    extends ServiceImpl<InteractionActorMapper, InteractionActor>
    implements InteractionActorService {

    @Override
    public InteractionActor resolveByUserOrDevice(Integer userId, Integer deviceId) {
        if (userId != null) {
            return getOne(Wrappers.<InteractionActor>lambdaQuery()
                .eq(InteractionActor::getReferenceId, userId)
                .eq(InteractionActor::getCategory, 1));
        }
        if (deviceId != null && deviceId != 0) {
            return getOne(Wrappers.<InteractionActor>lambdaQuery()
                .eq(InteractionActor::getReferenceId, deviceId)
                .eq(InteractionActor::getCategory, 0));
        }
        return null;
    }
}

Step 3: Persist Follow Record

@Mapper
public interface UserFollowActionMapper extends BaseMapper<UserFollowAction> {}

public interface FollowActionService extends IService<UserFollowAction> {
    ResponseResult recordFollow(FollowActionPayload payload);
}

@Slf4j
@Service
public class FollowActionServiceImpl
    extends ServiceImpl<UserFollowActionMapper, UserFollowAction>
    implements FollowActionService {

    @Autowired
    private InteractionActorService actorService;

    @Override
    public ResponseResult recordFollow(FollowActionPayload payload) {
        InteractionActor actor = actorService.resolveByUserOrDevice(payload.getUserId(), null);
        if (actor == null) {
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
        }
        UserFollowAction record = new UserFollowAction();
        record.setActorId(actor.getId());
        record.setActionTimestamp(new Date());
        record.setContentId(payload.getContentId());
        record.setTargetUserId(payload.getTargetUserId());
        save(record);
        return ResponseResult.okResult(record);
    }
}

Step 4: Kafka Consumer in Behavior Service

spring:
  kafka:
    bootstrap-servers: 192.168.200.130:9092
    consumer:
      group-id: ${spring.application.name}-kafka-group
      key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
      value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
@Component
public class FollowActionConsumer {

    @Autowired
    private FollowActionService followActionService;

    @KafkaListener(topics = EventTopics.FOLLOW_ACTION)
    public void onFollowEvent(ConsumerRecord<?, ?> record) {
        Optional<?> optional = Optional.ofNullable(record);
        if (optional.isPresent()) {
            FollowActionPayload payload = JSON.parseObject(
                record.value().toString(), FollowActionPayload.class);
            followActionService.recordFollow(payload);
        }
    }
}

Like Behavior Implementation

Requirements

When a logged-in user taps the like button, the action is persisted. Canceling a like does not delete the row—it flips the action_state from 0 (liked) to 1 (revoked).

Data Model

@Data
@TableName("user_like_action")
public class UserLikeAction implements Serializable {
    private static final long serialVersionUID = 1L;

    @TableId(value = "id", type = IdType.ID_WORKER)
    private Long id;

    @TableField("actor_id")
    private Integer actorId;

    @TableField("content_id")
    private Long contentId;

    @TableField("content_category")
    private Short contentCategory;

    @TableField("action_state")
    private Short actionState;

    @TableField("action_timestamp")
    private Date actionTimestamp;

    public enum ContentCategory {
        POST((short)0), FEED((short)1), REPLY((short)2);
        private final short code;
        ContentCategory(short code) { this.code = code; }
        public short getCode() { return code; }
    }

    public enum ActionState {
        ACTIVE((short)0), REVOKED((short)1);
        private final short code;
        ActionState(short code) { this.code = code; }
        public short getCode() { return code; }
    }
}

API Definition

public interface LikeActionApi {
    ResponseResult processLike(LikeActionPayload payload);
}

@Data
public class LikeActionPayload {
    @IdEncrypt
    private Integer deviceId;
    @IdEncrypt
    private Long contentId;
    private Short contentCategory;
    private Short actionState;
}

Authentication Filter

A servlet filter extracts the userId header and stores it in a thread-local utility, enabling downstream logic to access the authenticated user:

@Order(1)
@WebFilter(filterName = "authTokenFilter", urlPatterns = "/*")
public class AuthTokenFilter extends GenericFilterBean {
    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) req;
        String userIdHeader = request.getHeader("userId");
        if (userIdHeader != null && Integer.parseInt(userIdHeader) != 0) {
            AppUser user = new AppUser();
            user.setId(Integer.valueOf(userIdHeader));
            ThreadLocalContext.setCurrentUser(user);
        }
        chain.doFilter(req, res);
    }
}

Annotate the main application class with @ServletComponentScan to activate the filter.

Service Layer

public interface LikeActionService extends IService<UserLikeAction> {
    ResponseResult processLike(LikeActionPayload payload);
}

@Service
public class LikeActionServiceImpl
    extends ServiceImpl<UserLikeActionMapper, UserLikeAction>
    implements LikeActionService {

    @Autowired
    private InteractionActorService actorService;

    @Override
    public ResponseResult processLike(LikeActionPayload payload) {
        if (payload == null || payload.getContentId() == null
                || payload.getContentCategory() < 0 || payload.getContentCategory() > 2
                || payload.getActionState() < 0 || payload.getActionState() > 1) {
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
        }
        AppUser currentUser = ThreadLocalContext.getCurrentUser();
        InteractionActor actor = actorService.resolveByUserOrDevice(
            currentUser.getId(), payload.getDeviceId());
        if (actor == null) {
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
        }

        UserLikeAction existing = getOne(Wrappers.<UserLikeAction>lambdaQuery()
            .eq(UserLikeAction::getContentId, payload.getContentId())
            .eq(UserLikeAction::getActorId, actor.getId()));

        if (existing == null && payload.getActionState() == 0) {
            UserLikeAction newAction = new UserLikeAction();
            newAction.setActionState(payload.getActionState());
            newAction.setContentId(payload.getContentId());
            newAction.setActorId(actor.getId());
            newAction.setContentCategory(payload.getContentCategory());
            newAction.setActionTimestamp(new Date());
            save(newAction);
        } else if (existing != null) {
            existing.setActionState(payload.getActionState());
            updateById(existing);
        }
        return ResponseResult.okResult(AppHttpCodeEnum.SUCCESS);
    }
}

Controller

@RestController
@RequestMapping("/api/v1/like_action")
public class LikeActionController implements LikeActionApi {

    @Autowired
    private LikeActionService likeActionService;

    @PostMapping
    @Override
    public ResponseResult processLike(@RequestBody LikeActionPayload payload) {
        return likeActionService.processLike(payload);
    }
}

Add the behavior service route in the gateway configuration:

- id: event-tracker-service
  uri: lb://event-tracker-service
  predicates:
    - Path=/behavior/**
  filters:
    - StripPrefix= 1

Read Behavior Implementation

Requirements

Record how many times a user reads an article, the reading duration in seconds, the scroll percentage, and optionally the page load duration. Each subsequent read increments the count field on the existing record.

Data Model

@Data
@TableName("user_read_action")
public class UserReadAction implements Serializable {
    private static final long serialVersionUID = 1L;

    @TableId(value = "id", type = IdType.ID_WORKER)
    private Long id;

    @TableField("actor_id")
    private Integer actorId;

    @TableField("content_id")
    private Long contentId;

    @TableField("read_count")
    private Short readCount;

    @TableField("duration_seconds")
    private Integer durationSeconds;

    @TableField("scroll_percentage")
    private Short scrollPercentage;

    @TableField("load_time")
    private Short loadTime;

    @TableField("created_at")
    private Date createdAt;

    @TableField("updated_at")
    private Date updatedAt;
}

API and Payload

public interface ReadActionApi {
    ResponseResult recordRead(ReadActionPayload payload);
}

@Data
public class ReadActionPayload {
    @IdEncrypt
    private Integer deviceId;
    @IdEncrypt
    private Long contentId;
    private Short readCount;
    private Integer durationSeconds;
    private Short scrollPercentage;
    private Short loadTime;
}

Service Layer

public interface ReadActionService extends IService<UserReadAction> {
    ResponseResult recordRead(ReadActionPayload payload);
}

@Service
public class ReadActionServiceImpl
    extends ServiceImpl<UserReadActionMapper, UserReadAction>
    implements ReadActionService {

    @Autowired
    private InteractionActorService actorService;

    @Override
    public ResponseResult recordRead(ReadActionPayload payload) {
        if (payload == null || payload.getContentId() == null) {
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
        }
        AppUser currentUser = ThreadLocalContext.getCurrentUser();
        InteractionActor actor = actorService.resolveByUserOrDevice(
            currentUser.getId(), payload.getDeviceId());
        if (actor == null) {
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
        }

        UserReadAction existing = getOne(Wrappers.<UserReadAction>lambdaQuery()
            .eq(UserReadAction::getActorId, actor.getId())
            .eq(UserReadAction::getContentId, payload.getContentId()));

        if (existing == null) {
            UserReadAction newRecord = new UserReadAction();
            newRecord.setReadCount(payload.getReadCount());
            newRecord.setContentId(payload.getContentId());
            newRecord.setScrollPercentage(payload.getScrollPercentage());
            newRecord.setActorId(actor.getId());
            newRecord.setLoadTime(payload.getLoadTime());
            newRecord.setDurationSeconds(payload.getDurationSeconds());
            newRecord.setCreatedAt(new Date());
            save(newRecord);
        } else {
            existing.setUpdatedAt(new Date());
            existing.setReadCount((short)(existing.getReadCount() + 1));
            updateById(existing);
        }
        return ResponseResult.okResult(AppHttpCodeEnum.SUCCESS);
    }
}

Controller

@RestController
@RequestMapping("/api/v1/read_action")
public class ReadActionController implements ReadActionApi {

    @Autowired
    private ReadActionService readActionService;

    @PostMapping
    @Override
    public ResponseResult recordRead(@RequestBody ReadActionPayload payload) {
        return readActionService.recordRead(payload);
    }
}

Dislike and Bookmark Behavior Patterns

Dislike Behavior

The dislike mechanism prevents recommendation of certain content categories to the user. The user_dislike_action table tracks this with an action_state field: 0 for active dislike, 1 for canceled dislike.

@Data
@TableName("user_dislike_action")
public class UserDislikeAction implements Serializable {
    private static final long serialVersionUID = 1L;

    @TableId(value = "id", type = IdType.ID_WORKER)
    private Long id;

    @TableField("actor_id")
    private Integer actorId;

    @TableField("content_id")
    private Long contentId;

    @TableField("action_state")
    private Integer actionState;

    @TableField("action_timestamp")
    private Date actionTimestamp;

    public enum ActionState {
        ACTIVE((short)0), REVOKED((short)1);
        private final short code;
        ActionState(short code) { this.code = code; }
        public short getCode() { return code; }
    }
}

Implementation pattern: resolve the interaction actor, then either insert a new row or toggle action_state on the existing record. The endpoint is /api/v1/dislike_action (POST).

@Data
public class DislikeActionPayload {
    @IdEncrypt
    private Integer deviceId;
    @IdEncrypt
    private Long contentId;
    private Short actionState;
}

Bookmark Behavior

Bookmarks reside in the article database rather than the behavior database because users need to retrieve their saved article lists from the profile section. The content_bookmark table records entries with a content_category (post vs. feed) and a bookmark_timestamp.

@Data
@TableName("content_bookmark")
public class ContentBookmark implements Serializable {
    private static final long serialVersionUID = 1L;

    @TableId(value = "id", type = IdType.ID_WORKER)
    private Long id;

    @TableField("actor_id")
    private Integer actorId;

    @TableField("content_id")
    private Long contentId;

    @TableField("content_category")
    private Short contentCategory;

    @TableField("bookmark_timestamp")
    private Date bookmarkTimestamp;

    @TableField("publish_timestamp")
    private Date publishTimestamp;

    public enum ContentCategory {
        POST((short)0), FEED((short)1);
        private final short code;
        ContentCategory(short code) { this.code = code; }
        public short getCode() { return code; }
    }

    public boolean isPostBookmark() {
        return contentCategory != null && contentCategory.equals(ContentCategory.POST.getCode());
    }
}

Implementation pattern: within the article microservice, fetch the interaction actor via a remote call, then persist or update the bookmark. Avoid duplicate bookmarks by checking for existing records before insertion.

@Data
public class BookmarkPayload {
    @IdEncrypt
    private Integer deviceId;
    @IdEncrypt
    private Long contentId;
    private Short contentCategory;
    private Short operation;
    private Date publishTimestamp;
}

Article Interaction State Display

Requirements

When a logged-in user opens an article detail page, the UI must reflect whether the user has followed the author, liked the article, disliked it, or bookmarked it. Each affirmative state highlights its corresponding button.

Implementation Strategy

  1. Resolve the interaction actor from the user ID or device ID.
  2. Query the like, dislike, and bookmark tables using the actor ID and article ID. Like and dislike data reside in the behavior service and require Feign calls; bookmark data is local to the article service.
  3. Query the author table to obtain the author's user ID, then call the user service to check for a follow relationship.
  4. Return a JSON map: {"isfollow": true, "islike": true, "isunlike": false, "iscollection": true}

Remote Interface Preparation

Behavior Service Endpoints

Expose internal query endpoints in the behavior service for Feign consumption. These return domain objects directly rather than ResponseResult wrappers.

@RestController
@RequestMapping("/api/v1/actor")
public class InteractionActorController implements InteractionActorApi {
    @Autowired
    private InteractionActorService actorService;

    @GetMapping("/resolve")
    @Override
    public InteractionActor resolveByUserOrDevice(
            @RequestParam("userId") Integer userId,
            @RequestParam("deviceId") Integer deviceId) {
        return actorService.resolveByUserOrDevice(userId, deviceId);
    }
}
@RestController
@RequestMapping("/api/v1/like_action")
public class LikeActionController implements LikeActionApi {
    @Autowired
    private LikeActionService likeActionService;

    @GetMapping("/query")
    public UserLikeAction queryByArticleAndActor(
            @RequestParam("contentId") Long contentId,
            @RequestParam("actorId") Integer actorId,
            @RequestParam("category") Short category) {
        return likeActionService.getOne(Wrappers.<UserLikeAction>lambdaQuery()
            .eq(UserLikeAction::getContentId, contentId)
            .eq(UserLikeAction::getActorId, actorId)
            .eq(UserLikeAction::getContentCategory, category));
    }
}

Dislike Endpoint

@Mapper
public interface UserDislikeActionMapper extends BaseMapper<UserDislikeAction> {}

public interface DislikeActionService extends IService<UserDislikeAction> {}

@Service
public class DislikeActionServiceImpl
    extends ServiceImpl<UserDislikeActionMapper, UserDislikeAction>
    implements DislikeActionService {}

@RestController
@RequestMapping("/api/v1/dislike_action")
public class DislikeActionController implements DislikeActionApi {
    @Autowired
    private DislikeActionService dislikeActionService;

    @GetMapping("/query")
    @Override
    public UserDislikeAction queryByArticleAndActor(
            @RequestParam("actorId") Integer actorId,
            @RequestParam("contentId") Long contentId) {
        return dislikeActionService.getOne(Wrappers.<UserDislikeAction>lambdaQuery()
            .eq(UserDislikeAction::getContentId, contentId)
            .eq(UserDislikeAction::getActorId, actorId));
    }
}

User Service: Follow Check

@Mapper
public interface UserFollowMapper extends BaseMapper<UserFollowRecord> {}

public interface UserFollowService extends IService<UserFollowRecord> {}

@Service
public class UserFollowServiceImpl
    extends ServiceImpl<UserFollowMapper, UserFollowRecord>
    implements UserFollowService {}

@RestController
@RequestMapping("/api/v1/user_follow")
public class UserFollowController implements UserFollowApi {
    @Autowired
    private UserFollowService followService;

    @GetMapping("/check")
    @Override
    public UserFollowRecord checkFollowRelation(
            @RequestParam("userId") Integer userId,
            @RequestParam("authorUserId") Integer authorUserId) {
        return followService.getOne(Wrappers.<UserFollowRecord>lambdaQuery()
            .eq(UserFollowRecord::getUserId, userId)
            .eq(UserFollowRecord::getFollowId, authorUserId));
    }
}

Article Service: Feign Clients

Add the OpenFeign dependency to the article service:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
@FeignClient("event-tracker-service")
public interface BehaviorClient {
    @GetMapping("/api/v1/actor/resolve")
    InteractionActor resolveActor(@RequestParam("userId") Integer userId,
                                 @RequestParam("deviceId") Integer deviceId);

    @GetMapping("/api/v1/dislike_action/query")
    UserDislikeAction queryDislike(@RequestParam("actorId") Integer actorId,
                                  @RequestParam("contentId") Long contentId);

    @GetMapping("/api/v1/like_action/query")
    UserLikeAction queryLike(@RequestParam("actorId") Integer actorId,
                            @RequestParam("contentId") Long contentId,
                            @RequestParam("category") short category);
}

@FeignClient("user-service")
public interface UserClient {
    @GetMapping("/api/v1/user_follow/check")
    UserFollowRecord checkFollow(@RequestParam("userId") Integer userId,
                                @RequestParam("authorUserId") Integer authorUserId);
}

Annotate the article application class with @EnableFeignClients.

Article Interaction State Endpoint

API Definition

public interface ArticleDetailApi {
    ResponseResult loadInteractionState(ArticleDetailPayload payload);
}

@Data
public class ArticleDetailPayload {
    @IdEncrypt
    private Integer deviceId;
    @IdEncrypt
    private Long contentId;
    @IdEncrypt
    private Integer authorId;
}

Bookmark Mapper (local to article service)

@Mapper
public interface ContentBookmarkMapper extends BaseMapper<ContentBookmark> {}

Auth Filter in Article Service

@Order(1)
@WebFilter(filterName = "authTokenFilter", urlPatterns = "/*")
public class AuthTokenFilter extends GenericFilterBean {
    Logger logger = LoggerFactory.getLogger(AuthTokenFilter.class);

    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) req;
        String userIdHeader = request.getHeader("userId");
        if (userIdHeader != null && Integer.parseInt(userIdHeader) != 0) {
            AppUser user = new AppUser();
            user.setId(Long.valueOf(userIdHeader));
            ThreadLocalContext.setCurrentUser(user);
        }
        chain.doFilter(req, res);
    }
}

Enable the filter by adding @ServletComponentScan to the application class.

Service Implementation

@Autowired
private BehaviorClient behaviorClient;
@Autowired
private ContentBookmarkMapper bookmarkMapper;
@Autowired
private UserClient userClient;
@Autowired
private AuthorMapper authorMapper;

@Override
public ResponseResult loadInteractionState(ArticleDetailPayload payload) {
    if (payload == null || payload.getContentId() == null) {
        return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
    }
    AppUser currentUser = ThreadLocalContext.getCurrentUser();
    InteractionActor actor = behaviorClient.resolveActor(currentUser.getId(), payload.getDeviceId());
    if (actor == null) {
        return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
    }

    boolean isFollow = false, isLike = false, isDislike = false, isBookmark = false;

    UserDislikeAction dislike = behaviorClient.queryDislike(actor.getId(), payload.getContentId());
    if (dislike != null && dislike.getActionState() == UserDislikeAction.ActionState.ACTIVE.getCode()) {
        isDislike = true;
    }

    UserLikeAction like = behaviorClient.queryLike(actor.getId(), payload.getContentId(), UserLikeAction.ContentCategory.POST.getCode());
    if (like != null && like.getActionState() == UserLikeAction.ActionState.ACTIVE.getCode()) {
        isLike = true;
    }

    ContentBookmark bookmark = bookmarkMapper.selectOne(Wrappers.<ContentBookmark>lambdaQuery()
        .eq(ContentBookmark::getActorId, actor.getId())
        .eq(ContentBookmark::getContentId, payload.getContentId())
        .eq(ContentBookmark::getContentCategory, ContentBookmark.ContentCategory.POST.getCode()));
    if (bookmark != null) {
        isBookmark = true;
    }

    Author author = authorMapper.selectById(payload.getAuthorId());
    if (author != null) {
        UserFollowRecord followRecord = userClient.checkFollow(currentUser.getId(), author.getUserId());
        if (followRecord != null) {
            isFollow = true;
        }
    }

    Map<String, Object> stateMap = new HashMap<>();
    stateMap.put("isfollow", isFollow);
    stateMap.put("islike", isLike);
    stateMap.put("isunlike", isDislike);
    stateMap.put("iscollection", isBookmark);
    return ResponseResult.okResult(stateMap);
}

Controller

@PostMapping("/load_article_behavior")
@Override
public ResponseResult loadInteractionState(@RequestBody ArticleDetailPayload payload) {
    return articleDetailService.loadInteractionState(payload);
}

Related Articles

Efficient Usage of HTTP Client in IntelliJ IDEA

IntelliJ IDEA incorporates a versatile HTTP client tool, enabling developres to interact with RESTful services and APIs effectively with in the editor. This functionality streamlines workflows, replac...

Installing CocoaPods on macOS Catalina (10.15) Using a User-Managed Ruby

System Ruby on macOS 10.15 frequently fails to build native gems required by CocoaPods (for example, ffi), leading to errors like: ERROR: Failed to build gem native extension checking for ffi.h... no...

Resolve PhpStorm "Interpreter is not specified or invalid" on WAMP (Windows)

Symptom PhpStorm displays: "Interpreter is not specified or invalid. Press ‘Fix’ to edit your project configuration." This occurs when the IDE cannot locate a valid PHP CLI executable or when the debu...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.