|
@@ -12,10 +12,12 @@ import com.aijiuyi.admin.entity.AppUser;
|
|
|
import com.aijiuyi.admin.entity.Plan;
|
|
import com.aijiuyi.admin.entity.Plan;
|
|
|
import com.aijiuyi.admin.entity.PlanStep;
|
|
import com.aijiuyi.admin.entity.PlanStep;
|
|
|
import com.aijiuyi.admin.entity.UserPlan;
|
|
import com.aijiuyi.admin.entity.UserPlan;
|
|
|
|
|
+import com.aijiuyi.admin.entity.UserProfile;
|
|
|
import com.aijiuyi.admin.mapper.AcupointMapper;
|
|
import com.aijiuyi.admin.mapper.AcupointMapper;
|
|
|
import com.aijiuyi.admin.mapper.PlanMapper;
|
|
import com.aijiuyi.admin.mapper.PlanMapper;
|
|
|
import com.aijiuyi.admin.mapper.PlanStepMapper;
|
|
import com.aijiuyi.admin.mapper.PlanStepMapper;
|
|
|
import com.aijiuyi.admin.mapper.UserPlanMapper;
|
|
import com.aijiuyi.admin.mapper.UserPlanMapper;
|
|
|
|
|
+import com.aijiuyi.admin.mapper.UserProfileMapper;
|
|
|
import com.aijiuyi.admin.service.AppUserService;
|
|
import com.aijiuyi.admin.service.AppUserService;
|
|
|
import com.aijiuyi.admin.service.PlanService;
|
|
import com.aijiuyi.admin.service.PlanService;
|
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
@@ -58,6 +60,9 @@ public class PlanServiceImpl extends ServiceImpl<PlanMapper, Plan> implements Pl
|
|
|
@Autowired
|
|
@Autowired
|
|
|
private AppUserService appUserService;
|
|
private AppUserService appUserService;
|
|
|
|
|
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private UserProfileMapper userProfileMapper;
|
|
|
|
|
+
|
|
|
/**
|
|
/**
|
|
|
* 分页查询方案列表
|
|
* 分页查询方案列表
|
|
|
*
|
|
*
|
|
@@ -453,12 +458,45 @@ public class PlanServiceImpl extends ServiceImpl<PlanMapper, Plan> implements Pl
|
|
|
step.setStepOrder(i + 1);
|
|
step.setStepOrder(i + 1);
|
|
|
step.setAcupointId(acupoint.getId());
|
|
step.setAcupointId(acupoint.getId());
|
|
|
step.setAcupointName(acupoint.getName());
|
|
step.setAcupointName(acupoint.getName());
|
|
|
- // 侧别跟随穴位,无侧别信息时默认为 1(中心)
|
|
|
|
|
- step.setSide(acupoint.getSide() != null ? acupoint.getSide() : 1);
|
|
|
|
|
|
|
+ // 优先使用用户选择的侧别,并根据穴位单/双属性校验合法性
|
|
|
|
|
+ step.setSide(resolveStepSide(step.getSide(), acupoint));
|
|
|
planStepMapper.insert(step);
|
|
planStepMapper.insert(step);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 解析并校验步骤侧别
|
|
|
|
|
+ * - 单穴(singleDouble 包含'单')只允许侧别 1(中心)
|
|
|
|
|
+ * - 双穴(singleDouble 包含'双')不允许侧别 1,仅允许 2/3/4
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param requestSide 前端传入的侧别
|
|
|
|
|
+ * @param acupoint 穴位信息
|
|
|
|
|
+ * @return 合法的侧别值
|
|
|
|
|
+ */
|
|
|
|
|
+ private Integer resolveStepSide(Integer requestSide, Acupoint acupoint) {
|
|
|
|
|
+ String singleDouble = acupoint != null ? acupoint.getSingleDouble() : null;
|
|
|
|
|
+ boolean isSingle = singleDouble != null && singleDouble.contains("单");
|
|
|
|
|
+ boolean isDouble = singleDouble != null && singleDouble.contains("双");
|
|
|
|
|
+
|
|
|
|
|
+ if (requestSide != null) {
|
|
|
|
|
+ if (isSingle && !Integer.valueOf(1).equals(requestSide)) {
|
|
|
|
|
+ throw new BusinessException(ResultCode.PARAM_ERROR.getCode(), "单穴只能选择中心");
|
|
|
|
|
+ }
|
|
|
|
|
+ if (isDouble && Integer.valueOf(1).equals(requestSide)) {
|
|
|
|
|
+ throw new BusinessException(ResultCode.PARAM_ERROR.getCode(), "双穴不能选择中心");
|
|
|
|
|
+ }
|
|
|
|
|
+ if (isSingle || isDouble) {
|
|
|
|
|
+ return requestSide;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 无传入或无法识别单双属性时,使用穴位表侧别兜底
|
|
|
|
|
+ if (acupoint != null && acupoint.getSide() != null) {
|
|
|
|
|
+ return acupoint.getSide();
|
|
|
|
|
+ }
|
|
|
|
|
+ return 1;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
private Acupoint getActiveAcupoint(Long acupointId) {
|
|
private Acupoint getActiveAcupoint(Long acupointId) {
|
|
|
if (acupointId == null) {
|
|
if (acupointId == null) {
|
|
|
throw new BusinessException(ResultCode.ACUPOINT_NOT_FOUND);
|
|
throw new BusinessException(ResultCode.ACUPOINT_NOT_FOUND);
|
|
@@ -482,23 +520,32 @@ public class PlanServiceImpl extends ServiceImpl<PlanMapper, Plan> implements Pl
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * 用户新增自定义方案(含步骤),强制设置 modeType=3、authorType=2、authorId=userId
|
|
|
|
|
|
|
+ * 用户新增自定义方案(含步骤),author_id 取子用户ID(user_profile.id)
|
|
|
*
|
|
*
|
|
|
- * @param dto 方案信息
|
|
|
|
|
- * @param userId 用户ID
|
|
|
|
|
|
|
+ * @param dto 方案信息(需包含 profileId 指定归属子用户)
|
|
|
|
|
+ * @param appUserId APP账号ID(app_user.id,用于校验子用户归属)
|
|
|
* @return 方案ID
|
|
* @return 方案ID
|
|
|
*/
|
|
*/
|
|
|
@Override
|
|
@Override
|
|
|
@Transactional(rollbackFor = Exception.class)
|
|
@Transactional(rollbackFor = Exception.class)
|
|
|
- public Long createUserPlan(PlanSaveDTO dto, Long userId) {
|
|
|
|
|
|
|
+ public Long createUserPlan(PlanSaveDTO dto, Long appUserId) {
|
|
|
if (dto.getSteps() == null || dto.getSteps().isEmpty()) {
|
|
if (dto.getSteps() == null || dto.getSteps().isEmpty()) {
|
|
|
throw new BusinessException(ResultCode.PLAN_STEP_MIN_ERROR);
|
|
throw new BusinessException(ResultCode.PLAN_STEP_MIN_ERROR);
|
|
|
}
|
|
}
|
|
|
- // 方案名称仅在当前用户范围内做唯一性校验:不同用户可以起相同的方案名(方案 ID 各自不同)
|
|
|
|
|
|
|
+ // 校验子用户档案ID必传且归属当前APP账号
|
|
|
|
|
+ Long profileId = dto.getProfileId();
|
|
|
|
|
+ if (profileId == null) {
|
|
|
|
|
+ throw new BusinessException(ResultCode.PARAM_ERROR.getCode(), "子用户档案ID不能为空");
|
|
|
|
|
+ }
|
|
|
|
|
+ UserProfile profile = userProfileMapper.selectById(profileId);
|
|
|
|
|
+ if (profile == null || !appUserId.equals(profile.getAppUserId())) {
|
|
|
|
|
+ throw new BusinessException(ResultCode.PARAM_ERROR.getCode(), "子用户档案不存在或不属于当前账号");
|
|
|
|
|
+ }
|
|
|
|
|
+ // 方案名称仅在当前子用户范围内做唯一性校验:不同子用户可以起相同的方案名
|
|
|
if (StringUtils.hasText(dto.getName())) {
|
|
if (StringUtils.hasText(dto.getName())) {
|
|
|
long nameCount = lambdaQuery()
|
|
long nameCount = lambdaQuery()
|
|
|
.eq(Plan::getName, dto.getName())
|
|
.eq(Plan::getName, dto.getName())
|
|
|
- .eq(Plan::getAuthorId, userId)
|
|
|
|
|
|
|
+ .eq(Plan::getAuthorId, profileId)
|
|
|
.count();
|
|
.count();
|
|
|
if (nameCount > 0) {
|
|
if (nameCount > 0) {
|
|
|
throw new BusinessException(ResultCode.PLAN_NAME_EXISTS);
|
|
throw new BusinessException(ResultCode.PLAN_NAME_EXISTS);
|
|
@@ -507,56 +554,58 @@ public class PlanServiceImpl extends ServiceImpl<PlanMapper, Plan> implements Pl
|
|
|
String planCode = "S" + UUID.randomUUID().toString().replace("-", "").substring(0, 6).toUpperCase();
|
|
String planCode = "S" + UUID.randomUUID().toString().replace("-", "").substring(0, 6).toUpperCase();
|
|
|
Plan plan = new Plan();
|
|
Plan plan = new Plan();
|
|
|
BeanUtils.copyProperties(dto, plan);
|
|
BeanUtils.copyProperties(dto, plan);
|
|
|
- // 强制设置为自定义模式,创作人为当前用户
|
|
|
|
|
|
|
+ // 强制设置为自定义模式,创作人为当前子用户
|
|
|
plan.setModeType(MODE_TYPE_CUSTOM);
|
|
plan.setModeType(MODE_TYPE_CUSTOM);
|
|
|
plan.setPlanCode(planCode);
|
|
plan.setPlanCode(planCode);
|
|
|
plan.setStatus(dto.getStatus() != null ? dto.getStatus() : 1);
|
|
plan.setStatus(dto.getStatus() != null ? dto.getStatus() : 1);
|
|
|
plan.setUseCount(0);
|
|
plan.setUseCount(0);
|
|
|
- // 创作人信息根据当前登录用户自动填充(id、name、type)
|
|
|
|
|
- fillCurrentAuthor(plan);
|
|
|
|
|
- // 安全兜底:避免 fillCurrentAuthor 未设置(异常情况)导致 authorId 缺失
|
|
|
|
|
- plan.setAuthorId(userId);
|
|
|
|
|
- // 强制设置 authorType=2(用户创作),与 APP 端"我的自定义方案"过滤条件一致
|
|
|
|
|
|
|
+ // 创作人信息取子用户档案(id、name、type)
|
|
|
|
|
+ plan.setAuthorId(profileId);
|
|
|
|
|
+ plan.setAuthorName(profile.getName());
|
|
|
plan.setAuthorType(2);
|
|
plan.setAuthorType(2);
|
|
|
save(plan);
|
|
save(plan);
|
|
|
saveSteps(plan.getId(), dto.getSteps());
|
|
saveSteps(plan.getId(), dto.getSteps());
|
|
|
- LogUtil.info(PlanServiceImpl.class, "用户[{}]新增自定义方案[{}],编码[{}]", userId, plan.getName(), planCode);
|
|
|
|
|
|
|
+ LogUtil.info(PlanServiceImpl.class, "子用户[{}]新增自定义方案[{}],编码[{}]", profileId, plan.getName(), planCode);
|
|
|
return plan.getId();
|
|
return plan.getId();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * 查询用户的自定义模式方案列表(modeType=3,authorType=2,authorId=userId)
|
|
|
|
|
|
|
+ * 查询APP账号下所有子用户的自定义模式方案列表(modeType=3,authorType=2)
|
|
|
* 仅返回方案基本信息,不含穴位步骤;如需步骤请调用 getUserPlanDetail
|
|
* 仅返回方案基本信息,不含穴位步骤;如需步骤请调用 getUserPlanDetail
|
|
|
*
|
|
*
|
|
|
- * @param userId 用户ID
|
|
|
|
|
|
|
+ * @param appUserId APP账号ID(app_user.id)
|
|
|
* @return 方案列表
|
|
* @return 方案列表
|
|
|
*/
|
|
*/
|
|
|
@Override
|
|
@Override
|
|
|
- public List<Plan> listUserCustomPlans(Long userId) {
|
|
|
|
|
|
|
+ public List<Plan> listUserCustomPlans(Long appUserId) {
|
|
|
|
|
+ List<Long> profileIds = getProfileIdsByAppUserId(appUserId);
|
|
|
|
|
+ if (profileIds.isEmpty()) {
|
|
|
|
|
+ return java.util.Collections.emptyList();
|
|
|
|
|
+ }
|
|
|
return lambdaQuery()
|
|
return lambdaQuery()
|
|
|
.eq(Plan::getModeType, MODE_TYPE_CUSTOM)
|
|
.eq(Plan::getModeType, MODE_TYPE_CUSTOM)
|
|
|
.eq(Plan::getAuthorType, 2)
|
|
.eq(Plan::getAuthorType, 2)
|
|
|
- .eq(Plan::getAuthorId, userId)
|
|
|
|
|
|
|
+ .in(Plan::getAuthorId, profileIds)
|
|
|
.orderByDesc(Plan::getUpdateTime)
|
|
.orderByDesc(Plan::getUpdateTime)
|
|
|
.list();
|
|
.list();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * 查询用户自定义方案详情(含穴位步骤),校验方案归属
|
|
|
|
|
|
|
+ * 查询用户自定义方案详情(含穴位步骤),校验方案归属于当前APP账号的子用户
|
|
|
*
|
|
*
|
|
|
- * @param planId 方案ID
|
|
|
|
|
- * @param userId 用户ID
|
|
|
|
|
|
|
+ * @param planId 方案ID
|
|
|
|
|
+ * @param appUserId APP账号ID(app_user.id)
|
|
|
* @return 方案详情
|
|
* @return 方案详情
|
|
|
*/
|
|
*/
|
|
|
@Override
|
|
@Override
|
|
|
- public AppPlanVO getUserPlanDetail(Long planId, Long userId) {
|
|
|
|
|
|
|
+ public AppPlanVO getUserPlanDetail(Long planId, Long appUserId) {
|
|
|
Plan plan = getById(planId);
|
|
Plan plan = getById(planId);
|
|
|
if (plan == null) {
|
|
if (plan == null) {
|
|
|
throw new BusinessException(ResultCode.PLAN_NOT_FOUND);
|
|
throw new BusinessException(ResultCode.PLAN_NOT_FOUND);
|
|
|
}
|
|
}
|
|
|
if (!Integer.valueOf(MODE_TYPE_CUSTOM).equals(plan.getModeType())
|
|
if (!Integer.valueOf(MODE_TYPE_CUSTOM).equals(plan.getModeType())
|
|
|
|| !Integer.valueOf(2).equals(plan.getAuthorType())
|
|
|| !Integer.valueOf(2).equals(plan.getAuthorType())
|
|
|
- || !userId.equals(plan.getAuthorId())) {
|
|
|
|
|
|
|
+ || !isPlanOwnedByAppUser(plan, appUserId)) {
|
|
|
throw new BusinessException(ResultCode.PLAN_NOT_FOUND);
|
|
throw new BusinessException(ResultCode.PLAN_NOT_FOUND);
|
|
|
}
|
|
}
|
|
|
AppPlanVO vo = new AppPlanVO();
|
|
AppPlanVO vo = new AppPlanVO();
|
|
@@ -566,26 +615,24 @@ public class PlanServiceImpl extends ServiceImpl<PlanMapper, Plan> implements Pl
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * 用户修改自己的自定义方案(含步骤),仅允许修改自己的自定义模式方案
|
|
|
|
|
|
|
+ * 用户修改自己的自定义方案(含步骤),校验方案归属于当前APP账号的子用户
|
|
|
*
|
|
*
|
|
|
- * @param dto 方案信息
|
|
|
|
|
- * @param userId 用户ID
|
|
|
|
|
|
|
+ * @param dto 方案信息
|
|
|
|
|
+ * @param appUserId APP账号ID(app_user.id)
|
|
|
*/
|
|
*/
|
|
|
@Override
|
|
@Override
|
|
|
@Transactional(rollbackFor = Exception.class)
|
|
@Transactional(rollbackFor = Exception.class)
|
|
|
- public void updateUserPlan(PlanSaveDTO dto, Long userId) {
|
|
|
|
|
|
|
+ public void updateUserPlan(PlanSaveDTO dto, Long appUserId) {
|
|
|
Plan plan = getById(dto.getId());
|
|
Plan plan = getById(dto.getId());
|
|
|
if (plan == null) {
|
|
if (plan == null) {
|
|
|
throw new BusinessException(ResultCode.PLAN_NOT_FOUND);
|
|
throw new BusinessException(ResultCode.PLAN_NOT_FOUND);
|
|
|
}
|
|
}
|
|
|
- // 仅允许修改自己的自定义模式方案
|
|
|
|
|
|
|
+ // 仅允许修改属于当前APP账号子用户的自定义模式方案
|
|
|
if (!Integer.valueOf(MODE_TYPE_CUSTOM).equals(plan.getModeType())
|
|
if (!Integer.valueOf(MODE_TYPE_CUSTOM).equals(plan.getModeType())
|
|
|
|| !Integer.valueOf(2).equals(plan.getAuthorType())
|
|
|| !Integer.valueOf(2).equals(plan.getAuthorType())
|
|
|
- || !userId.equals(plan.getAuthorId())) {
|
|
|
|
|
|
|
+ || !isPlanOwnedByAppUser(plan, appUserId)) {
|
|
|
throw new BusinessException(ResultCode.PLAN_NOT_FOUND);
|
|
throw new BusinessException(ResultCode.PLAN_NOT_FOUND);
|
|
|
}
|
|
}
|
|
|
- // 修改接口不做名称重名校验:方案 ID 已经存在,用户在已有方案上"任意修改",
|
|
|
|
|
- // 不同用户的自定义方案本就允许同名(方案 ID 不同即可区分)
|
|
|
|
|
// 更新方案基本信息(仅允许修改的可编辑字段)
|
|
// 更新方案基本信息(仅允许修改的可编辑字段)
|
|
|
if (StringUtils.hasText(dto.getName())) {
|
|
if (StringUtils.hasText(dto.getName())) {
|
|
|
plan.setName(dto.getName());
|
|
plan.setName(dto.getName());
|
|
@@ -604,30 +651,58 @@ public class PlanServiceImpl extends ServiceImpl<PlanMapper, Plan> implements Pl
|
|
|
saveSteps(dto.getId(), dto.getSteps());
|
|
saveSteps(dto.getId(), dto.getSteps());
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
- LogUtil.info(PlanServiceImpl.class, "用户[{}]修改自定义方案[{}]", userId, dto.getId());
|
|
|
|
|
|
|
+ LogUtil.info(PlanServiceImpl.class, "APP账号[{}]修改自定义方案[{}]", appUserId, dto.getId());
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * 用户删除自己的自定义方案(仅允许删除 modeType=3、authorType=2、authorId=userId 的方案)
|
|
|
|
|
|
|
+ * 用户删除自己的自定义方案,校验方案归属于当前APP账号的子用户
|
|
|
*
|
|
*
|
|
|
- * @param planId 方案ID
|
|
|
|
|
- * @param userId 用户ID
|
|
|
|
|
|
|
+ * @param planId 方案ID
|
|
|
|
|
+ * @param appUserId APP账号ID(app_user.id)
|
|
|
*/
|
|
*/
|
|
|
@Override
|
|
@Override
|
|
|
@Transactional(rollbackFor = Exception.class)
|
|
@Transactional(rollbackFor = Exception.class)
|
|
|
- public void deleteUserPlan(Long planId, Long userId) {
|
|
|
|
|
|
|
+ public void deleteUserPlan(Long planId, Long appUserId) {
|
|
|
Plan plan = getById(planId);
|
|
Plan plan = getById(planId);
|
|
|
if (plan == null) {
|
|
if (plan == null) {
|
|
|
throw new BusinessException(ResultCode.PLAN_NOT_FOUND);
|
|
throw new BusinessException(ResultCode.PLAN_NOT_FOUND);
|
|
|
}
|
|
}
|
|
|
if (!Integer.valueOf(MODE_TYPE_CUSTOM).equals(plan.getModeType())
|
|
if (!Integer.valueOf(MODE_TYPE_CUSTOM).equals(plan.getModeType())
|
|
|
|| !Integer.valueOf(2).equals(plan.getAuthorType())
|
|
|| !Integer.valueOf(2).equals(plan.getAuthorType())
|
|
|
- || !userId.equals(plan.getAuthorId())) {
|
|
|
|
|
|
|
+ || !isPlanOwnedByAppUser(plan, appUserId)) {
|
|
|
throw new BusinessException(ResultCode.PLAN_NOT_FOUND);
|
|
throw new BusinessException(ResultCode.PLAN_NOT_FOUND);
|
|
|
}
|
|
}
|
|
|
removeById(planId);
|
|
removeById(planId);
|
|
|
planStepMapper.delete(new LambdaQueryWrapper<PlanStep>().eq(PlanStep::getPlanId, planId));
|
|
planStepMapper.delete(new LambdaQueryWrapper<PlanStep>().eq(PlanStep::getPlanId, planId));
|
|
|
- LogUtil.info(PlanServiceImpl.class, "用户[{}]删除自定义方案[{}]", userId, planId);
|
|
|
|
|
|
|
+ LogUtil.info(PlanServiceImpl.class, "APP账号[{}]删除自定义方案[{}]", appUserId, planId);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 查询APP账号下所有子用户档案ID
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param appUserId APP账号ID
|
|
|
|
|
+ * @return 子用户档案ID列表
|
|
|
|
|
+ */
|
|
|
|
|
+ private List<Long> getProfileIdsByAppUserId(Long appUserId) {
|
|
|
|
|
+ List<UserProfile> profiles = userProfileMapper.selectList(
|
|
|
|
|
+ new LambdaQueryWrapper<UserProfile>().eq(UserProfile::getAppUserId, appUserId)
|
|
|
|
|
+ );
|
|
|
|
|
+ return profiles.stream().map(UserProfile::getId).collect(java.util.stream.Collectors.toList());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 校验方案是否归属于当前APP账号的子用户
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param plan 方案实体
|
|
|
|
|
+ * @param appUserId APP账号ID
|
|
|
|
|
+ * @return true=归属当前账号
|
|
|
|
|
+ */
|
|
|
|
|
+ private boolean isPlanOwnedByAppUser(Plan plan, Long appUserId) {
|
|
|
|
|
+ if (plan.getAuthorId() == null) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ List<Long> profileIds = getProfileIdsByAppUserId(appUserId);
|
|
|
|
|
+ return profileIds.contains(plan.getAuthorId());
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|