|
|
@@ -0,0 +1,211 @@
|
|
|
+package com.etotem.cfc.service;
|
|
|
+
|
|
|
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
|
|
+import com.etotem.cfc.common.DataMaskUtil;
|
|
|
+import com.etotem.cfc.dto.OrgMemberVO;
|
|
|
+import com.etotem.cfc.entity.OrgMember;
|
|
|
+import com.etotem.cfc.entity.User;
|
|
|
+import com.etotem.cfc.mapper.OrgMemberMapper;
|
|
|
+import com.etotem.cfc.mapper.UserMapper;
|
|
|
+import org.springframework.jdbc.core.JdbcTemplate;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+import org.springframework.transaction.annotation.Transactional;
|
|
|
+
|
|
|
+import javax.annotation.Resource;
|
|
|
+import java.util.ArrayList;
|
|
|
+import java.util.Collections;
|
|
|
+import java.util.Date;
|
|
|
+import java.util.HashMap;
|
|
|
+import java.util.List;
|
|
|
+import java.util.Map;
|
|
|
+import java.util.stream.Collectors;
|
|
|
+
|
|
|
+@Service
|
|
|
+public class OrgMemberService extends ServiceImpl<OrgMemberMapper, OrgMember> {
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private UserMapper userMapper;
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private OrgMemberMapper orgMemberMapper;
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private JdbcTemplate jdbcTemplate;
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private UserSearchService userSearchService;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 成员列表(含家庭数/订单数/消费总额摘要,默认只返回 status=active)。
|
|
|
+ * 聚合基于 userId 集合批量 IN 查询,禁止 per-member 循环查询。
|
|
|
+ */
|
|
|
+ public List<OrgMemberVO> memberList(Long orgId) {
|
|
|
+ // 1) 查活跃成员基础信息(Mapper 联查 users 表带出 nickname/phone/avatar)
|
|
|
+ List<OrgMemberVO> baseList = orgMemberMapper.selectActiveMembersWithUser(orgId);
|
|
|
+ if (baseList.isEmpty()) {
|
|
|
+ return Collections.emptyList();
|
|
|
+ }
|
|
|
+ List<Long> userIds = baseList.stream()
|
|
|
+ .map(OrgMemberVO::getUserId)
|
|
|
+ .distinct()
|
|
|
+ .collect(Collectors.toList());
|
|
|
+
|
|
|
+ // 2) 批量聚合:家庭数(family_members.user_id IN ... 去重计数)
|
|
|
+ Map<Long, Integer> familyCountMap = batchFamilyCount(userIds);
|
|
|
+ // 3) 批量聚合:有效订单数
|
|
|
+ Map<Long, Integer> totalOrdersMap = batchTotalOrders(userIds);
|
|
|
+ // 4) 批量聚合:消费总额(分)
|
|
|
+ Map<Long, Long> totalConsumptionMap = batchTotalConsumption(userIds);
|
|
|
+
|
|
|
+ // 5) 组装 VO(手机号脱敏)
|
|
|
+ for (OrgMemberVO vo : baseList) {
|
|
|
+ Long uid = vo.getUserId();
|
|
|
+ vo.setFamilyCount(familyCountMap.getOrDefault(uid, 0));
|
|
|
+ vo.setTotalOrders(totalOrdersMap.getOrDefault(uid, 0));
|
|
|
+ vo.setTotalConsumption(totalConsumptionMap.getOrDefault(uid, 0L));
|
|
|
+ if (vo.getPhone() != null) {
|
|
|
+ vo.setPhone(DataMaskUtil.maskPhone(vo.getPhone()));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return baseList;
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 添加成员:已存在记录(含 inactive)则复活并更新类型;不存在则插入 */
|
|
|
+ @Transactional
|
|
|
+ public boolean addMember(Long orgId, Long userId, String memberType) {
|
|
|
+ if (orgId == null || userId == null) {
|
|
|
+ throw new IllegalArgumentException("orgId/userId 不能为空");
|
|
|
+ }
|
|
|
+ OrgMember existing = orgMemberMapper.selectOne(new LambdaQueryWrapper<OrgMember>()
|
|
|
+ .eq(OrgMember::getOrgId, orgId)
|
|
|
+ .eq(OrgMember::getUserId, userId));
|
|
|
+ if (existing != null) {
|
|
|
+ boolean wasInactive = "inactive".equals(existing.getStatus());
|
|
|
+ existing.setMemberType(memberType);
|
|
|
+ existing.setStatus("active");
|
|
|
+ if (wasInactive) {
|
|
|
+ existing.setJoinedAt(new Date());
|
|
|
+ }
|
|
|
+ return orgMemberMapper.updateById(existing) > 0;
|
|
|
+ }
|
|
|
+ OrgMember member = new OrgMember();
|
|
|
+ member.setOrgId(orgId);
|
|
|
+ member.setUserId(userId);
|
|
|
+ member.setMemberType(memberType);
|
|
|
+ member.setStatus("active");
|
|
|
+ member.setJoinedAt(new Date());
|
|
|
+ return orgMemberMapper.insert(member) > 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 移除成员(软删,status=inactive) */
|
|
|
+ @Transactional
|
|
|
+ public boolean removeMember(Long orgId, Long userId) {
|
|
|
+ OrgMember existing = orgMemberMapper.selectOne(new LambdaQueryWrapper<OrgMember>()
|
|
|
+ .eq(OrgMember::getOrgId, orgId)
|
|
|
+ .eq(OrgMember::getUserId, userId));
|
|
|
+ if (existing == null) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ existing.setStatus("inactive");
|
|
|
+ return orgMemberMapper.updateById(existing) > 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 变更成员类型(planner ↔ user),同时会复活 inactive 成员为 active */
|
|
|
+ @Transactional
|
|
|
+ public boolean changeType(Long orgId, Long userId, String memberType) {
|
|
|
+ if (memberType == null
|
|
|
+ || (!"planner".equals(memberType) && !"user".equals(memberType))) {
|
|
|
+ throw new IllegalArgumentException("memberType 仅支持 planner/user");
|
|
|
+ }
|
|
|
+ OrgMember existing = orgMemberMapper.selectOne(new LambdaQueryWrapper<OrgMember>()
|
|
|
+ .eq(OrgMember::getOrgId, orgId)
|
|
|
+ .eq(OrgMember::getUserId, userId));
|
|
|
+ if (existing == null) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ boolean wasInactive = "inactive".equals(existing.getStatus());
|
|
|
+ existing.setMemberType(memberType);
|
|
|
+ existing.setStatus("active");
|
|
|
+ if (wasInactive) {
|
|
|
+ existing.setJoinedAt(new Date());
|
|
|
+ }
|
|
|
+ return orgMemberMapper.updateById(existing) > 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 按手机号/昵称搜索平台用户(添加成员时下拉选人用),委托 UserSearchService */
|
|
|
+ public List<Map<String, Object>> userSearch(String keyword, String memberType) {
|
|
|
+ return userSearchService.search(keyword, memberType);
|
|
|
+ }
|
|
|
+
|
|
|
+ // ========== 批量聚合辅助 ==========
|
|
|
+
|
|
|
+ private Map<Long, Integer> batchFamilyCount(List<Long> userIds) {
|
|
|
+ if (userIds == null || userIds.isEmpty()) {
|
|
|
+ return Collections.emptyMap();
|
|
|
+ }
|
|
|
+ String in = buildInPlaceholder(userIds.size());
|
|
|
+ return jdbcTemplate.query(
|
|
|
+ "SELECT user_id, COUNT(DISTINCT id) AS cnt FROM family_members " +
|
|
|
+ "WHERE user_id IN (" + in + ") GROUP BY user_id",
|
|
|
+ rs -> {
|
|
|
+ Map<Long, Integer> map = new HashMap<>();
|
|
|
+ while (rs.next()) {
|
|
|
+ map.put(rs.getLong("user_id"), rs.getInt("cnt"));
|
|
|
+ }
|
|
|
+ return map;
|
|
|
+ }, userIds.toArray());
|
|
|
+ }
|
|
|
+
|
|
|
+ private Map<Long, Integer> batchTotalOrders(List<Long> userIds) {
|
|
|
+ if (userIds == null || userIds.isEmpty()) {
|
|
|
+ return Collections.emptyMap();
|
|
|
+ }
|
|
|
+ String in = buildInPlaceholder(userIds.size());
|
|
|
+ return jdbcTemplate.query(
|
|
|
+ "SELECT buyer_id, COUNT(*) AS cnt FROM product_orders " +
|
|
|
+ "WHERE buyer_id IN (" + in + ") AND status IN ('PAID','COMPLETED') " +
|
|
|
+ "GROUP BY buyer_id",
|
|
|
+ rs -> {
|
|
|
+ Map<Long, Integer> map = new HashMap<>();
|
|
|
+ while (rs.next()) {
|
|
|
+ map.put(rs.getLong("buyer_id"), rs.getInt("cnt"));
|
|
|
+ }
|
|
|
+ return map;
|
|
|
+ }, userIds.toArray());
|
|
|
+ }
|
|
|
+
|
|
|
+ private Map<Long, Long> batchTotalConsumption(List<Long> userIds) {
|
|
|
+ if (userIds == null || userIds.isEmpty()) {
|
|
|
+ return Collections.emptyMap();
|
|
|
+ }
|
|
|
+ String in = buildInPlaceholder(userIds.size());
|
|
|
+ return jdbcTemplate.query(
|
|
|
+ "SELECT buyer_id, SUM(total_amount) AS total FROM product_orders " +
|
|
|
+ "WHERE buyer_id IN (" + in + ") AND status IN ('PAID','COMPLETED') " +
|
|
|
+ "GROUP BY buyer_id",
|
|
|
+ rs -> {
|
|
|
+ Map<Long, Long> map = new HashMap<>();
|
|
|
+ while (rs.next()) {
|
|
|
+ long sum = rs.getLong("total");
|
|
|
+ // SUM 可能为 NULL,用 wasNull 兜底
|
|
|
+ if (rs.wasNull()) {
|
|
|
+ sum = 0L;
|
|
|
+ }
|
|
|
+ map.put(rs.getLong("buyer_id"), sum);
|
|
|
+ }
|
|
|
+ return map;
|
|
|
+ }, userIds.toArray());
|
|
|
+ }
|
|
|
+
|
|
|
+ private String buildInPlaceholder(int cnt) {
|
|
|
+ StringBuilder sb = new StringBuilder();
|
|
|
+ for (int i = 0; i < cnt; i++) {
|
|
|
+ if (i > 0) {
|
|
|
+ sb.append(",");
|
|
|
+ }
|
|
|
+ sb.append("?");
|
|
|
+ }
|
|
|
+ return sb.toString();
|
|
|
+ }
|
|
|
+}
|