新增cp告白信封功能

This commit is contained in:
tianfeng 2026-01-13 18:40:00 +08:00
parent 6035415969
commit 9319ec228a
18 changed files with 629 additions and 4 deletions

View File

@ -252,6 +252,11 @@ public enum OfficialNoticeTypeEnum {
*/
FAMILY_APPLY_APPROVAL,
/**
* CP告白信封
*/
CP_LOVE_LETTER,
;

View File

@ -67,6 +67,11 @@ public enum UserRelationErrorCode implements IResponseErrorCode {
*/
UNAVAILABLE_NOT_REGION(4312, "Unavailable to operate not same region"),
/**
* CP关系不存在.
*/
CP_RELATIONSHIP_NOT_EXISTS(4313, "CP relationship does not exist"),
;

View File

@ -734,9 +734,14 @@ public enum GoldOrigin {
FAMILY_BOX_REWARD("Family box reward"),
/**
* 房间红包
* 用户红包
*/
USER_RED_PACKET("User red packet"),
/**
* CP告白信封
*/
CP_LOVE_LETTER("CP love letter"),
;
private final String desc;

View File

@ -2,14 +2,15 @@ package com.red.circle.other.adapter.app.user.relation;
import com.red.circle.common.business.dto.cmd.AppExtCommand;
import com.red.circle.common.business.dto.cmd.UserIdCmd;
import com.red.circle.common.business.dto.cmd.UserIdCommonCmd;
import com.red.circle.framework.web.controller.BaseController;
import com.red.circle.other.app.dto.clientobject.user.relation.cp.CpCabinUserProfileCO;
import com.red.circle.other.app.command.user.LoveLetterQueryCmdExe;
import com.red.circle.other.app.dto.clientobject.user.relation.cp.CpPairUserProfileCO;
import com.red.circle.other.app.dto.clientobject.user.relation.cp.CpUserProfileCO;
import com.red.circle.other.domain.model.cp.loveletter.LoveLetterDetailCO;
import com.red.circle.other.domain.model.cp.loveletter.LoveLetterWallCO;
import com.red.circle.other.app.dto.cmd.user.relation.cp.CpApplyCmd;
import com.red.circle.other.app.dto.cmd.user.relation.cp.CpApplyDismissCmd;
import com.red.circle.other.app.dto.cmd.user.relation.cp.ProcessApplyCmd;
import com.red.circle.other.app.dto.cmd.user.relation.cp.loveletter.SendLoveLetterCmd;
import com.red.circle.other.app.service.user.relation.UserCpRelationService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.MediaType;
@ -39,6 +40,7 @@ import java.util.List;
public class CpRelationshipRestController extends BaseController {
private final UserCpRelationService userCpRelationService;
private final LoveLetterQueryCmdExe loveLetterQueryCmdExe;
/**
* 获取我的-cp对象.
@ -105,4 +107,43 @@ public class CpRelationshipRestController extends BaseController {
return userCpRelationService.getCpApplyId(cmd);
}
/**
* 发送CP告白信封.
*
* @eo.name 发送CP告白信封
* @eo.url /send-love-letter
* @eo.method post
* @eo.request-type json
*/
@PostMapping("/send-love-letter")
public void sendLoveLetter(@RequestBody @Validated SendLoveLetterCmd cmd) {
userCpRelationService.sendLoveLetter(cmd);
}
/**
* 查询告白墙.
*
* @eo.name 查询告白墙
* @eo.url /love-letter-wall
* @eo.method get
* @eo.request-type formdata
*/
@GetMapping("/love-letter-wall")
public List<LoveLetterWallCO> getLoveLetterWall() {
return loveLetterQueryCmdExe.queryWall();
}
/**
* 查询我收到的最新告白信封.
*
* @eo.name 查询我收到的最新告白信封
* @eo.url /latest-love-letter
* @eo.method get
* @eo.request-type formdata
*/
@GetMapping("/latest-love-letter")
public LoveLetterDetailCO getLatestLoveLetter(AppExtCommand cmd) {
return loveLetterQueryCmdExe.queryLatestReceived(cmd);
}
}

View File

@ -0,0 +1,99 @@
package com.red.circle.other.app.command.user;
import com.red.circle.common.business.dto.cmd.AppExtCommand;
import com.red.circle.other.domain.gateway.user.CpLoveLetterGateway;
import com.red.circle.other.domain.gateway.user.UserProfileGateway;
import com.red.circle.other.domain.model.cp.CpLoveLetterDomain;
import com.red.circle.other.domain.model.cp.loveletter.LoveLetterDetailCO;
import com.red.circle.other.domain.model.cp.loveletter.LoveLetterWallCO;
import com.red.circle.other.domain.model.user.UserProfile;
import com.red.circle.other.infra.convertor.user.CpLoveLetterConvertor;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* 查询CP告白信封
*/
@Component
@RequiredArgsConstructor
public class LoveLetterQueryCmdExe {
private final CpLoveLetterGateway cpLoveLetterGateway;
private final UserProfileGateway userProfileGateway;
private final CpLoveLetterConvertor cpLoveLetterConvertor;
/**
* 查询告白墙最新50条
*/
public List<LoveLetterWallCO> queryWall() {
List<CpLoveLetterDomain> letters = cpLoveLetterGateway.listLatestLetters(50);
if (letters.isEmpty()) {
return new ArrayList<>();
}
// 批量查询发送者信息
Set<Long> senderIds = letters.stream()
.map(CpLoveLetterDomain::getSenderId)
.collect(Collectors.toSet());
// 批量查询发送者信息
Set<Long> receiverIds = letters.stream()
.map(CpLoveLetterDomain::getReceiverId)
.collect(Collectors.toSet());
senderIds.addAll(receiverIds);
Map<Long, UserProfile> userProfileMap = userProfileGateway.listByUserIds(senderIds)
.stream()
.collect(Collectors.toMap(UserProfile::getId, Function.identity()));
// 组装返回数据
return letters.stream()
.map(letter -> {
LoveLetterWallCO co = cpLoveLetterConvertor.toWallCO(letter);
UserProfile profile = userProfileMap.get(letter.getSenderId());
if (Objects.nonNull(profile)) {
co.setSenderNickname(profile.getUserNickname());
co.setSenderAvatar(profile.getUserAvatar());
}
UserProfile receiver = userProfileMap.get(letter.getReceiverId());
if (Objects.nonNull(receiver)) {
co.setReceiverNickname(receiver.getUserNickname());
co.setReceiverAvatar(receiver.getUserAvatar());
}
return co;
})
.collect(Collectors.toList());
}
/**
* 查询我收到的最新信封
*/
public LoveLetterDetailCO queryLatestReceived(AppExtCommand cmd) {
Long userId = cmd.requiredReqUserId();
CpLoveLetterDomain letter = cpLoveLetterGateway.getLatestReceivedLetter(userId);
if (Objects.isNull(letter)) {
return null;
}
// 标记为已读
if (letter.isUnread()) {
cpLoveLetterGateway.markAsRead(letter.getId());
}
// 查询发送者信息
UserProfile senderProfile = userProfileGateway.getByUserId(letter.getSenderId());
LoveLetterDetailCO co = cpLoveLetterConvertor.toDetailCO(letter);
if (Objects.nonNull(senderProfile)) {
co.setSenderNickname(senderProfile.getUserNickname());
co.setSenderAvatar(senderProfile.getUserAvatar());
}
return co;
}
}

View File

@ -0,0 +1,108 @@
package com.red.circle.other.app.command.user;
import com.red.circle.external.inner.endpoint.message.OfficialNoticeClient;
import com.red.circle.external.inner.model.cmd.message.notice.official.NoticeExtTemplateTypeCmd;
import com.red.circle.external.inner.model.enums.message.OfficialNoticeTypeEnum;
import com.red.circle.framework.core.asserts.ResponseAssert;
import com.red.circle.other.app.convertor.user.UserProfileAppConvertor;
import com.red.circle.other.app.dto.cmd.user.relation.cp.loveletter.SendLoveLetterCmd;
import com.red.circle.other.app.util.OfficialNoticeUtils;
import com.red.circle.other.domain.gateway.user.CpLoveLetterGateway;
import com.red.circle.other.domain.gateway.user.UserProfileGateway;
import com.red.circle.other.domain.model.cp.CpLoveLetterDomain;
import com.red.circle.other.infra.database.rds.service.user.user.CpRelationshipService;
import com.red.circle.other.inner.asserts.user.UserRelationErrorCode;
import com.red.circle.other.inner.model.dto.user.UserProfileDTO;
import com.red.circle.tool.core.date.TimestampUtils;
import com.red.circle.tool.core.tuple.PennyAmount;
import com.red.circle.wallet.inner.endpoint.wallet.WalletGoldClient;
import com.red.circle.wallet.inner.error.WalletErrorCode;
import com.red.circle.wallet.inner.model.cmd.GoldReceiptCmd;
import com.red.circle.wallet.inner.model.enums.GoldOrigin;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.util.Map;
/**
* 发送CP告白信封
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class SendLoveLetterCmdExe {
private final CpLoveLetterGateway cpLoveLetterGateway;
private final CpRelationshipService cpRelationshipService;
private final WalletGoldClient walletGoldClient;
private final OfficialNoticeClient officialNoticeClient;
private final UserProfileGateway userProfileGateway;
private final UserProfileAppConvertor userProfileAppConvertor;
private static final Long LOVE_LETTER_COST = 5000L;
@Transactional(rollbackFor = Exception.class)
public void execute(SendLoveLetterCmd cmd) {
Long senderId = cmd.requiredReqUserId();
Long receiverId = cmd.getReceiverId();
// 校验双方是否已建立CP关系
boolean cpExists = cpRelationshipService.existsCp(senderId, receiverId);
ResponseAssert.isTrue(UserRelationErrorCode.CP_RELATIONSHIP_NOT_EXISTS, cpExists);
// 扣除金币
deductGold(senderId, cmd);
// 保存信封
CpLoveLetterDomain letter = new CpLoveLetterDomain();
letter.setSenderId(senderId);
letter.setReceiverId(receiverId);
letter.setContent(cmd.getContent());
letter.setStatus(0);
letter.setCostGold(LOVE_LETTER_COST);
letter.setCreateTime(TimestampUtils.now());
letter.setUpdateTime(TimestampUtils.now());
cpLoveLetterGateway.save(letter);
// 发送System消息通知
sendNotification(senderId, receiverId, cmd.getContent(), letter.getId());
}
private void deductGold(Long userId, SendLoveLetterCmd cmd) {
GoldReceiptCmd build = GoldReceiptCmd.builder()
.appExpenditure()
.userId(userId)
.amount(PennyAmount.ofDollar(LOVE_LETTER_COST))
.eventId(String.valueOf(userId))
.sysOrigin(cmd.getReqSysOrigin().getOrigin())
.origin(GoldOrigin.CP_LOVE_LETTER)
.build();
try {
walletGoldClient.changeBalance(build);
} catch (Exception e) {
log.error("扣除金币失败 userId={} cost={}", userId, LOVE_LETTER_COST, e);
ResponseAssert.isTrue(WalletErrorCode.INSUFFICIENT_BALANCE, false);
}
}
private void sendNotification(Long senderId, Long receiverId, String content, Long letterId) {
UserProfileDTO senderProfile = userProfileAppConvertor.toUserProfileDTO(
userProfileGateway.getByUserId(senderId)
);
Map<Object, Object> templateParam = OfficialNoticeUtils.buildUserProfile(senderProfile);
templateParam.put("content", content);
officialNoticeClient.send(NoticeExtTemplateTypeCmd.builder()
.toAccount(receiverId)
.noticeType(OfficialNoticeTypeEnum.CP_LOVE_LETTER)
.templateParam(templateParam)
.expand(letterId)
.build()
);
}
}

View File

@ -4,8 +4,10 @@ import com.red.circle.common.business.dto.cmd.AppExtCommand;
import com.red.circle.common.business.dto.cmd.UserIdCmd;
import com.red.circle.common.business.dto.cmd.UserIdCommonCmd;
import com.red.circle.other.app.command.user.DismissCpApplyCmdExe;
import com.red.circle.other.app.command.user.LoveLetterQueryCmdExe;
import com.red.circle.other.app.command.user.ProcessCpApplyCmd;
import com.red.circle.other.app.command.user.SendCpApplyCmdExe;
import com.red.circle.other.app.command.user.SendLoveLetterCmdExe;
import com.red.circle.other.app.command.user.query.CpApplyIdQryExe;
import com.red.circle.other.app.command.user.query.UserCpPairUserProfileQryExe;
import com.red.circle.other.app.command.user.query.UserCpRelationQryExe;
@ -13,9 +15,12 @@ import com.red.circle.other.app.command.user.query.UserCpRelationQueryV4Exe;
import com.red.circle.other.app.dto.clientobject.user.relation.cp.CpCabinUserProfileCO;
import com.red.circle.other.app.dto.clientobject.user.relation.cp.CpPairUserProfileCO;
import com.red.circle.other.app.dto.clientobject.user.relation.cp.CpUserProfileCO;
import com.red.circle.other.domain.model.cp.loveletter.LoveLetterDetailCO;
import com.red.circle.other.domain.model.cp.loveletter.LoveLetterWallCO;
import com.red.circle.other.app.dto.cmd.user.relation.cp.CpApplyCmd;
import com.red.circle.other.app.dto.cmd.user.relation.cp.CpApplyDismissCmd;
import com.red.circle.other.app.dto.cmd.user.relation.cp.ProcessApplyCmd;
import com.red.circle.other.app.dto.cmd.user.relation.cp.loveletter.SendLoveLetterCmd;
import com.red.circle.other.app.util.DistributedLockUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@ -39,6 +44,8 @@ public class UserCpRelationServiceImpl implements UserCpRelationService {
private final UserCpPairUserProfileQryExe userCpPairUserProfileQryExe;
private final UserCpRelationQueryV4Exe userCpRelationQueryV4Exe;
private final DistributedLockUtil distributedLockUtil;
private final SendLoveLetterCmdExe sendLoveLetterCmdExe;
private final LoveLetterQueryCmdExe loveLetterQueryCmdExe;
@Override
public CpUserProfileCO getCpUser(UserIdCmd cmd) {
@ -81,5 +88,13 @@ public class UserCpRelationServiceImpl implements UserCpRelationService {
return userCpRelationQueryV4Exe.execute(cmd);
}
@Override
public void sendLoveLetter(SendLoveLetterCmd cmd) {
String key = "SEND_LOVE_LETTER:" + cmd.getReqUserId();
distributedLockUtil.executeWithLock(key, () -> {
sendLoveLetterCmdExe.execute(cmd);
});
}
}

View File

@ -0,0 +1,26 @@
package com.red.circle.other.app.dto.cmd.user.relation.cp.loveletter;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.red.circle.common.business.dto.cmd.AppExtCommand;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 发送CP告白信封
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class SendLoveLetterCmd extends AppExtCommand {
@NotNull(message = "接收者用户ID不能为空")
@JsonSerialize(using = ToStringSerializer.class)
private Long receiverId;
@NotBlank(message = "告白内容不能为空")
@Size(max = 80, message = "告白内容不能超过80字符")
private String content;
}

View File

@ -9,6 +9,7 @@ import com.red.circle.other.app.dto.clientobject.user.relation.cp.CpUserProfileC
import com.red.circle.other.app.dto.cmd.user.relation.cp.CpApplyCmd;
import com.red.circle.other.app.dto.cmd.user.relation.cp.CpApplyDismissCmd;
import com.red.circle.other.app.dto.cmd.user.relation.cp.ProcessApplyCmd;
import com.red.circle.other.app.dto.cmd.user.relation.cp.loveletter.SendLoveLetterCmd;
import java.util.List;
@ -33,5 +34,6 @@ public interface UserCpRelationService {
CpCabinUserProfileCO getCpUserV4(UserIdCommonCmd cmd);
void sendLoveLetter(SendLoveLetterCmd cmd);
}

View File

@ -0,0 +1,31 @@
package com.red.circle.other.domain.gateway.user;
import com.red.circle.other.domain.model.cp.CpLoveLetterDomain;
import java.util.List;
/**
* CP告白信封数据网关
*/
public interface CpLoveLetterGateway {
/**
* 保存信封
*/
void save(CpLoveLetterDomain letter);
/**
* 查询告白墙最新N条
*/
List<CpLoveLetterDomain> listLatestLetters(Integer limit);
/**
* 查询用户收到的最新一条信封
*/
CpLoveLetterDomain getLatestReceivedLetter(Long receiverId);
/**
* 更新信封状态为已读
*/
void markAsRead(Long letterId);
}

View File

@ -0,0 +1,43 @@
package com.red.circle.other.domain.model.cp;
import lombok.Data;
import java.sql.Timestamp;
import java.time.LocalDateTime;
/**
* CP告白信封领域模型
*/
@Data
public class CpLoveLetterDomain {
private Long id;
private Long senderId;
private Long receiverId;
private String content;
private Integer status;
private Long costGold;
private Timestamp createTime;
private Timestamp updateTime;
/**
* 是否未读
*/
public boolean isUnread() {
return status == 0;
}
/**
* 标记为已读
*/
public void markAsRead() {
this.status = 1;
}
}

View File

@ -0,0 +1,31 @@
package com.red.circle.other.domain.model.cp.loveletter;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.Data;
import java.sql.Timestamp;
import java.time.LocalDateTime;
/**
* 告白信封详情
*/
@Data
public class LoveLetterDetailCO {
@JsonSerialize(using = ToStringSerializer.class)
private Long id;
@JsonSerialize(using = ToStringSerializer.class)
private Long senderId;
private String senderNickname;
private String senderAvatar;
private String content;
private Integer status;
private Timestamp createTime;
}

View File

@ -0,0 +1,33 @@
package com.red.circle.other.domain.model.cp.loveletter;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.Data;
import java.sql.Timestamp;
import java.time.LocalDateTime;
/**
* 告白墙信封
*/
@Data
public class LoveLetterWallCO {
@JsonSerialize(using = ToStringSerializer.class)
private Long id;
@JsonSerialize(using = ToStringSerializer.class)
private Long senderId;
private String senderNickname;
private String senderAvatar;
private String receiverNickname;
private String receiverAvatar;
private String content;
private Timestamp createTime;
}

View File

@ -0,0 +1,17 @@
package com.red.circle.other.infra.convertor.user;
import com.red.circle.other.domain.model.cp.CpLoveLetterDomain;
import com.red.circle.other.domain.model.cp.loveletter.LoveLetterDetailCO;
import com.red.circle.other.domain.model.cp.loveletter.LoveLetterWallCO;
import org.mapstruct.Mapper;
/**
* CP告白信封转换器
*/
@Mapper(componentModel = "spring")
public interface CpLoveLetterConvertor {
LoveLetterWallCO toWallCO(CpLoveLetterDomain letter);
LoveLetterDetailCO toDetailCO(CpLoveLetterDomain letter);
}

View File

@ -0,0 +1,23 @@
package com.red.circle.other.infra.database.rds.dao.user.user;
import com.red.circle.framework.mybatis.dao.BaseDAO;
import com.red.circle.other.infra.database.rds.entity.user.user.CpLoveLetter;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* CP告白信封 Mapper
*/
public interface CpLoveLetterDAO extends BaseDAO<CpLoveLetter> {
/**
* 查询告白墙最新N条信封
*/
List<CpLoveLetter> listLatestLetters(@Param("limit") Integer limit);
/**
* 查询用户收到的最新一条信封
*/
CpLoveLetter getLatestReceivedLetter(@Param("receiverId") Long receiverId);
}

View File

@ -0,0 +1,37 @@
package com.red.circle.other.infra.database.rds.entity.user.user;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.time.LocalDateTime;
/**
* CP告白信封
*/
@Data
@TableName("cp_love_letter")
public class CpLoveLetter {
@TableId(type = IdType.AUTO)
private Long id;
private Long senderId;
private Long receiverId;
private String content;
private Integer status;
private Long costGold;
@TableLogic
private Integer isDeleted;
private LocalDateTime createTime;
private LocalDateTime updateTime;
}

View File

@ -0,0 +1,84 @@
package com.red.circle.other.infra.gateway.user;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.red.circle.other.domain.gateway.user.CpLoveLetterGateway;
import com.red.circle.other.domain.model.cp.CpLoveLetterDomain;
import com.red.circle.other.infra.database.rds.dao.user.user.CpLoveLetterDAO;
import com.red.circle.other.infra.database.rds.entity.user.user.CpLoveLetter;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import java.sql.Timestamp;
import java.util.List;
import java.util.stream.Collectors;
/**
* CP告白信封数据网关实现
*/
@Component
@RequiredArgsConstructor
public class CpLoveLetterGatewayImpl implements CpLoveLetterGateway {
private final CpLoveLetterDAO cpLoveLetterDAO;
@Override
public void save(CpLoveLetterDomain letter) {
CpLoveLetter entity = toEntity(letter);
cpLoveLetterDAO.insert(entity);
letter.setId(entity.getId());
}
@Override
public List<CpLoveLetterDomain> listLatestLetters(Integer limit) {
List<CpLoveLetter> entities = cpLoveLetterDAO.listLatestLetters(limit);
return entities.stream()
.map(this::toDomain)
.collect(Collectors.toList());
}
@Override
public CpLoveLetterDomain getLatestReceivedLetter(Long receiverId) {
CpLoveLetter entity = cpLoveLetterDAO.getLatestReceivedLetter(receiverId);
return entity == null ? null : toDomain(entity);
}
@Override
public void markAsRead(Long letterId) {
LambdaUpdateWrapper<CpLoveLetter> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(CpLoveLetter::getId, letterId)
.set(CpLoveLetter::getStatus, 1);
cpLoveLetterDAO.update(null, wrapper);
}
/**
* 领域模型转Entity
*/
private CpLoveLetter toEntity(CpLoveLetterDomain domain) {
CpLoveLetter entity = new CpLoveLetter();
entity.setId(domain.getId());
entity.setSenderId(domain.getSenderId());
entity.setReceiverId(domain.getReceiverId());
entity.setContent(domain.getContent());
entity.setStatus(domain.getStatus());
entity.setCostGold(domain.getCostGold());
entity.setCreateTime(domain.getCreateTime().toLocalDateTime());
entity.setUpdateTime(domain.getUpdateTime().toLocalDateTime());
return entity;
}
/**
* Entity转领域模型
*/
private CpLoveLetterDomain toDomain(CpLoveLetter entity) {
CpLoveLetterDomain domain = new CpLoveLetterDomain();
domain.setId(entity.getId());
domain.setSenderId(entity.getSenderId());
domain.setReceiverId(entity.getReceiverId());
domain.setContent(entity.getContent());
domain.setStatus(entity.getStatus());
domain.setCostGold(entity.getCostGold());
domain.setCreateTime(Timestamp.valueOf(entity.getCreateTime()));
domain.setUpdateTime(Timestamp.valueOf(entity.getUpdateTime()));
return domain;
}
}

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.red.circle.other.infra.database.rds.dao.user.user.CpLoveLetterDAO">
<select id="listLatestLetters" resultType="com.red.circle.other.infra.database.rds.entity.user.user.CpLoveLetter">
SELECT * FROM cp_love_letter
WHERE is_deleted = 0
ORDER BY create_time DESC
LIMIT #{limit}
</select>
<select id="getLatestReceivedLetter" resultType="com.red.circle.other.infra.database.rds.entity.user.user.CpLoveLetter">
SELECT * FROM cp_love_letter
WHERE receiver_id = #{receiverId}
AND is_deleted = 0
ORDER BY create_time DESC
LIMIT 1
</select>
</mapper>