feat: 搭建后端多租户名片服务

- 初始化 Spring Boot 多模块工程与通用基础能力

- 增加租户、组织、用户、名片、文件、统计等业务模块

- 补充数据库迁移脚本与基础测试
This commit is contained in:
2026-03-20 12:43:21 +08:00
parent 1a2a078c0f
commit 9ef50288e9
95 changed files with 6722 additions and 0 deletions

View File

@@ -0,0 +1,23 @@
package com.easycard.module.stat.dal.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@TableName("card_share_log")
public class CardShareLogDO {
@TableId(type = IdType.AUTO)
private Long id;
private Long tenantId;
private String miniappAppId;
private Long cardId;
private String shareChannel;
private String sharePath;
private String shareByOpenId;
private LocalDateTime sharedAt;
}

View File

@@ -0,0 +1,25 @@
package com.easycard.module.stat.dal.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.time.LocalDate;
import java.time.LocalDateTime;
@Data
@TableName("card_stat_daily")
public class CardStatDailyDO {
@TableId(type = IdType.AUTO)
private Long id;
private Long tenantId;
private Long cardId;
private LocalDate statDate;
private Long viewCount;
private Long shareCount;
private Long uniqueVisitorCount;
private LocalDateTime createdTime;
private LocalDateTime updatedTime;
}

View File

@@ -0,0 +1,25 @@
package com.easycard.module.stat.dal.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@TableName("card_view_log")
public class CardViewLogDO {
@TableId(type = IdType.AUTO)
private Long id;
private Long tenantId;
private String miniappAppId;
private Long cardId;
private String viewerOpenId;
private String viewerIp;
private String sourceType;
private Long shareFromCardId;
private String pagePath;
private LocalDateTime viewedAt;
}

View File

@@ -0,0 +1,7 @@
package com.easycard.module.stat.dal.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.easycard.module.stat.dal.entity.CardShareLogDO;
public interface CardShareLogMapper extends BaseMapper<CardShareLogDO> {
}

View File

@@ -0,0 +1,7 @@
package com.easycard.module.stat.dal.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.easycard.module.stat.dal.entity.CardStatDailyDO;
public interface CardStatDailyMapper extends BaseMapper<CardStatDailyDO> {
}

View File

@@ -0,0 +1,7 @@
package com.easycard.module.stat.dal.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.easycard.module.stat.dal.entity.CardViewLogDO;
public interface CardViewLogMapper extends BaseMapper<CardViewLogDO> {
}

View File

@@ -0,0 +1,77 @@
package com.easycard.module.stat.service;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.easycard.module.stat.dal.entity.CardShareLogDO;
import com.easycard.module.stat.dal.entity.CardStatDailyDO;
import com.easycard.module.stat.dal.entity.CardViewLogDO;
import com.easycard.module.stat.dal.mapper.CardShareLogMapper;
import com.easycard.module.stat.dal.mapper.CardStatDailyMapper;
import com.easycard.module.stat.dal.mapper.CardViewLogMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDate;
import java.time.LocalDateTime;
@Service
@RequiredArgsConstructor
public class CardEventService {
private final CardViewLogMapper cardViewLogMapper;
private final CardShareLogMapper cardShareLogMapper;
private final CardStatDailyMapper cardStatDailyMapper;
@Transactional
public void recordView(Long tenantId, String appId, Long cardId, String sourceType, Long shareFromCardId, String viewerIp, String pagePath) {
CardViewLogDO viewLog = new CardViewLogDO();
viewLog.setTenantId(tenantId);
viewLog.setMiniappAppId(appId);
viewLog.setCardId(cardId);
viewLog.setSourceType(sourceType);
viewLog.setShareFromCardId(shareFromCardId);
viewLog.setViewerIp(viewerIp);
viewLog.setPagePath(pagePath);
viewLog.setViewedAt(LocalDateTime.now());
cardViewLogMapper.insert(viewLog);
updateDailyStats(tenantId, cardId, true);
}
@Transactional
public void recordShare(Long tenantId, String appId, Long cardId, String shareChannel, String sharePath) {
CardShareLogDO shareLog = new CardShareLogDO();
shareLog.setTenantId(tenantId);
shareLog.setMiniappAppId(appId);
shareLog.setCardId(cardId);
shareLog.setShareChannel(shareChannel);
shareLog.setSharePath(sharePath);
shareLog.setSharedAt(LocalDateTime.now());
cardShareLogMapper.insert(shareLog);
updateDailyStats(tenantId, cardId, false);
}
private void updateDailyStats(Long tenantId, Long cardId, boolean viewIncrement) {
LocalDate statDate = LocalDate.now();
CardStatDailyDO statDaily = cardStatDailyMapper.selectOne(Wrappers.<CardStatDailyDO>lambdaQuery()
.eq(CardStatDailyDO::getTenantId, tenantId)
.eq(CardStatDailyDO::getCardId, cardId)
.eq(CardStatDailyDO::getStatDate, statDate)
.last("LIMIT 1"));
if (statDaily == null) {
statDaily = new CardStatDailyDO();
statDaily.setTenantId(tenantId);
statDaily.setCardId(cardId);
statDaily.setStatDate(statDate);
statDaily.setViewCount(0L);
statDaily.setShareCount(0L);
statDaily.setUniqueVisitorCount(0L);
cardStatDailyMapper.insert(statDaily);
}
if (viewIncrement) {
statDaily.setViewCount(statDaily.getViewCount() + 1);
} else {
statDaily.setShareCount(statDaily.getShareCount() + 1);
}
cardStatDailyMapper.updateById(statDaily);
}
}