diff --git a/im-admin-ui/src/views/im/userLabel/index.vue b/im-admin-ui/src/views/im/userLabel/index.vue index 0f031f2..f227021 100644 --- a/im-admin-ui/src/views/im/userLabel/index.vue +++ b/im-admin-ui/src/views/im/userLabel/index.vue @@ -29,11 +29,13 @@ 修改 - 删除 + 删除 - + @@ -109,30 +111,21 @@ const initFormData: UserLabelForm = { labelName: undefined, sort: undefined, remark: undefined -} +}; const data = reactive>({ - form: {...initFormData}, + form: { ...initFormData }, queryParams: { pageNum: 1, pageSize: 10, labelName: undefined, sort: undefined, - params: { - } + params: {} }, rules: { - id: [ - { required: true, message: "id不能为空", trigger: "blur" } - ], - labelName: [ - { required: true, message: "标签名称不能为空", trigger: "blur" } - ], - sort: [ - { required: true, message: "排序不能为空", trigger: "blur" } - ], - remark: [ - { required: true, message: "备注不能为空", trigger: "blur" } - ] + id: [{ required: true, message: 'id不能为空', trigger: 'blur' }], + labelName: [{ required: true, message: '标签名称不能为空', trigger: 'blur' }], + sort: [{ required: true, message: '排序不能为空', trigger: 'blur' }], + remark: [{ required: true, message: '备注不能为空', trigger: 'blur' }] } }); @@ -145,55 +138,55 @@ const getList = async () => { userLabelList.value = res.rows; total.value = res.total; loading.value = false; -} +}; /** 取消按钮 */ const cancel = () => { reset(); dialog.visible = false; -} +}; /** 表单重置 */ const reset = () => { - form.value = {...initFormData}; + form.value = { ...initFormData }; userLabelFormRef.value?.resetFields(); -} +}; /** 搜索按钮操作 */ const handleQuery = () => { queryParams.value.pageNum = 1; getList(); -} +}; /** 重置按钮操作 */ const resetQuery = () => { queryFormRef.value?.resetFields(); handleQuery(); -} +}; /** 多选框选中数据 */ const handleSelectionChange = (selection: UserLabelVO[]) => { - ids.value = selection.map(item => item.id); + ids.value = selection.map((item) => item.id); single.value = selection.length != 1; multiple.value = !selection.length; -} +}; /** 新增按钮操作 */ const handleAdd = () => { reset(); dialog.visible = true; - dialog.title = "添加用户分组"; -} + dialog.title = '添加用户分组'; +}; /** 修改按钮操作 */ const handleUpdate = async (row?: UserLabelVO) => { reset(); - const _id = row?.id || ids.value[0] + const _id = row?.id || ids.value[0]; const res = await getUserLabel(_id); Object.assign(form.value, res.data); dialog.visible = true; - dialog.title = "修改用户分组"; -} + dialog.title = '修改用户分组'; +}; /** 提交按钮 */ const submitForm = () => { @@ -201,32 +194,36 @@ const submitForm = () => { if (valid) { buttonLoading.value = true; if (form.value.id) { - await updateUserLabel(form.value).finally(() => buttonLoading.value = false); + await updateUserLabel(form.value).finally(() => (buttonLoading.value = false)); } else { - await addUserLabel(form.value).finally(() => buttonLoading.value = false); + await addUserLabel(form.value).finally(() => (buttonLoading.value = false)); } - proxy?.$modal.msgSuccess("操作成功"); + proxy?.$modal.msgSuccess('操作成功'); dialog.visible = false; await getList(); } }); -} +}; /** 删除按钮操作 */ const handleDelete = async (row?: UserLabelVO) => { const _ids = row?.id || ids.value; - await proxy?.$modal.confirm('是否确认删除用户分组编号为"' + _ids + '"的数据项?').finally(() => loading.value = false); + await proxy?.$modal.confirm('是否确认删除用户分组编号为"' + _ids + '"的数据项?').finally(() => (loading.value = false)); await delUserLabel(_ids); - proxy?.$modal.msgSuccess("删除成功"); + proxy?.$modal.msgSuccess('删除成功'); await getList(); -} +}; /** 导出按钮操作 */ const handleExport = () => { - proxy?.download('im/userLabel/export', { - ...queryParams.value - }, `userLabel_${new Date().getTime()}.xlsx`) -} + proxy?.download( + 'im/userLabel/export', + { + ...queryParams.value + }, + `userLabel_${new Date().getTime()}.xlsx` + ); +}; onMounted(() => { getList(); diff --git a/im-admin/ruoyi-im/src/main/java/org/dromara/im/constant/ImConstant.java b/im-admin/ruoyi-im/src/main/java/org/dromara/im/constant/ImConstant.java index 553501b..ab1054b 100644 --- a/im-admin/ruoyi-im/src/main/java/org/dromara/im/constant/ImConstant.java +++ b/im-admin/ruoyi-im/src/main/java/org/dromara/im/constant/ImConstant.java @@ -11,4 +11,9 @@ public class ImConstant { * IM数据源 */ public final static String DS_IM_PLATFORM = "platform"; + + /** + * 代理token缓存key前缀 + */ + public static final String AGENT_TOKEN_KEY = "agent_token:"; } diff --git a/im-admin/ruoyi-im/src/main/java/org/dromara/im/service/IImAgentService.java b/im-admin/ruoyi-im/src/main/java/org/dromara/im/service/IImAgentService.java index 27ccc08..0fa1f3b 100644 --- a/im-admin/ruoyi-im/src/main/java/org/dromara/im/service/IImAgentService.java +++ b/im-admin/ruoyi-im/src/main/java/org/dromara/im/service/IImAgentService.java @@ -69,9 +69,7 @@ public interface IImAgentService { /** * 获取代理用户的唯一token - * - * @param userId 用户id * @return 唯一token */ - String getTokenByUserId(Long userId); + String getTokenByUserId(); } diff --git a/im-admin/ruoyi-im/src/main/java/org/dromara/im/service/impl/ImAgentServiceImpl.java b/im-admin/ruoyi-im/src/main/java/org/dromara/im/service/impl/ImAgentServiceImpl.java index 0f4620d..de6a1e4 100644 --- a/im-admin/ruoyi-im/src/main/java/org/dromara/im/service/impl/ImAgentServiceImpl.java +++ b/im-admin/ruoyi-im/src/main/java/org/dromara/im/service/impl/ImAgentServiceImpl.java @@ -2,6 +2,7 @@ package org.dromara.im.service.impl; import cn.hutool.core.util.ObjectUtil; import com.baomidou.dynamic.datasource.annotation.DS; +import org.dromara.common.core.constant.GlobalConstants; import org.dromara.common.core.utils.MapstructUtils; import org.dromara.common.core.utils.StringUtils; import org.dromara.common.mybatis.core.page.TableDataInfo; @@ -10,6 +11,8 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import lombok.RequiredArgsConstructor; +import org.dromara.common.redis.utils.RedisUtils; +import org.dromara.common.satoken.utils.LoginHelper; import org.dromara.im.constant.ImConstant; import org.springframework.stereotype.Service; import org.dromara.im.domain.bo.ImAgentBo; @@ -18,10 +21,12 @@ import org.dromara.im.domain.ImAgent; import org.dromara.im.mapper.ImAgentMapper; import org.dromara.im.service.IImAgentService; +import java.time.Duration; import java.util.List; -import java.util.Map; import java.util.Collection; +import static org.dromara.im.constant.ImConstant.AGENT_TOKEN_KEY; + /** * 代理关联Service业务层处理 * @@ -136,16 +141,41 @@ public class ImAgentServiceImpl implements IImAgentService { } @Override - public String getTokenByUserId(Long userId) { + public String getTokenByUserId() { + Long userId = LoginHelper.getUserId(); + + if(userId == null){ + return "error"; + } + + if(userId == 1){ + return null; + } + + // 构建缓存key + String cacheKey = AGENT_TOKEN_KEY + userId; + + // 先从缓存中获取 + String cachedToken = RedisUtils.getCacheObject(cacheKey); + if (ObjectUtil.isNotEmpty(cachedToken)) { + return cachedToken; + } + + // 缓存未命中,查询数据库 ImAgent agent = baseMapper.selectOne(new LambdaQueryWrapper().eq(ImAgent::getSysId, userId)); + String token = "error"; if(ObjectUtil.isNotNull(agent)){ - String token = agent.getUniqueToken(); - if(ObjectUtil.isNotEmpty(token)){ - return token; + String uniqueToken = agent.getUniqueToken(); + if(ObjectUtil.isNotEmpty(uniqueToken)){ + token = uniqueToken; + // 将token存入缓存,过期时间1小时 + RedisUtils.setCacheObject(cacheKey, token, Duration.ofHours(1)); } } - return ""; + return token; } + + } diff --git a/im-admin/ruoyi-im/src/main/java/org/dromara/im/service/impl/ImUserLabelServiceImpl.java b/im-admin/ruoyi-im/src/main/java/org/dromara/im/service/impl/ImUserLabelServiceImpl.java index 6c34271..7ca163a 100644 --- a/im-admin/ruoyi-im/src/main/java/org/dromara/im/service/impl/ImUserLabelServiceImpl.java +++ b/im-admin/ruoyi-im/src/main/java/org/dromara/im/service/impl/ImUserLabelServiceImpl.java @@ -1,5 +1,6 @@ package org.dromara.im.service.impl; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import org.dromara.common.core.utils.MapstructUtils; import org.dromara.common.core.utils.StringUtils; import org.dromara.common.mybatis.core.page.TableDataInfo; @@ -9,7 +10,10 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.dynamic.datasource.annotation.DS; import lombok.RequiredArgsConstructor; +import org.dromara.common.satoken.utils.LoginHelper; import org.dromara.im.constant.ImConstant; +import org.dromara.im.service.IImAgentService; +import org.dromara.im.util.LambdaQueryWrapperHelper; import org.springframework.stereotype.Service; import org.dromara.im.domain.bo.ImUserLabelBo; import org.dromara.im.domain.vo.ImUserLabelVo; @@ -34,6 +38,8 @@ public class ImUserLabelServiceImpl implements IImUserLabelService { private final ImUserLabelMapper baseMapper; + private final IImAgentService imAgentService; + /** * 查询用户分组 * @@ -42,7 +48,14 @@ public class ImUserLabelServiceImpl implements IImUserLabelService { */ @Override public ImUserLabelVo queryById(Long id){ - return baseMapper.selectVoById(id); + LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); + lqw.eq(ImUserLabel::getId, id); + + if(!LoginHelper.isSuperAdmin()) { + LambdaQueryWrapperHelper.appendToken(lqw, ImUserLabel::getUniqueToken); + } + + return baseMapper.selectVoOne(lqw); } /** @@ -76,6 +89,11 @@ public class ImUserLabelServiceImpl implements IImUserLabelService { LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); lqw.like(StringUtils.isNotBlank(bo.getLabelName()), ImUserLabel::getLabelName, bo.getLabelName()); lqw.eq(bo.getSort() != null, ImUserLabel::getSort, bo.getSort()); + + if(!LoginHelper.isSuperAdmin()) { + LambdaQueryWrapperHelper.appendToken(lqw, ImUserLabel::getUniqueToken); + } + return lqw; } @@ -88,7 +106,14 @@ public class ImUserLabelServiceImpl implements IImUserLabelService { @Override public Boolean insertByBo(ImUserLabelBo bo) { ImUserLabel add = MapstructUtils.convert(bo, ImUserLabel.class); - validEntityBeforeSave(add); + + // 如果不是超级管理员,则设置 uniqueToken + if(!LoginHelper.isSuperAdmin()) { + if (add != null) { + add.setUniqueToken(imAgentService.getTokenByUserId()); + } + } + boolean flag = baseMapper.insert(add) > 0; if (flag) { bo.setId(add.getId()); @@ -105,15 +130,24 @@ public class ImUserLabelServiceImpl implements IImUserLabelService { @Override public Boolean updateByBo(ImUserLabelBo bo) { ImUserLabel update = MapstructUtils.convert(bo, ImUserLabel.class); - validEntityBeforeSave(update); - return baseMapper.updateById(update) > 0; - } + if (update == null) { + return false; + } - /** - * 保存前的数据校验 - */ - private void validEntityBeforeSave(ImUserLabel entity){ - //TODO 做一些数据校验,如唯一约束 + LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper<>(); + updateWrapper.eq(ImUserLabel::getId, update.getId()); + + + if(!LoginHelper.isSuperAdmin()) {//非超级管理员 + // 使用当前用户的 token 而不是从 bo 中获取,防止篡改 + updateWrapper.eq(ImUserLabel::getUniqueToken, imAgentService.getTokenByUserId()); + } + + updateWrapper.set(ImUserLabel::getLabelName, update.getLabelName()); + updateWrapper.set(ImUserLabel::getSort, update.getSort()); + updateWrapper.set(ImUserLabel::getRemark, update.getRemark()); + + return baseMapper.update(null, updateWrapper) > 0; } /** @@ -126,9 +160,19 @@ public class ImUserLabelServiceImpl implements IImUserLabelService { @Override public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { if(isValid){ - //TODO 做一些业务上的校验,判断是否需要校验 + if (ids == null || ids.isEmpty()) { + return false; + } } - return baseMapper.deleteByIds(ids) > 0; + + LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper<>(); + updateWrapper.in(ImUserLabel::getId, ids); + + if(!LoginHelper.isSuperAdmin()) {//非超级管理员 + // 添加 uniqueToken 条件,确保只能删除当前用户的记录 + updateWrapper.eq(ImUserLabel::getUniqueToken, imAgentService.getTokenByUserId()); + } + return baseMapper.delete(updateWrapper) > 0; } /** @@ -141,6 +185,13 @@ public class ImUserLabelServiceImpl implements IImUserLabelService { LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); lqw.select(ImUserLabel::getId, ImUserLabel::getLabelName); lqw.orderByAsc(ImUserLabel::getSort); + + //非超级管理员添加token条件 + if(!LoginHelper.isSuperAdmin()) { + LambdaQueryWrapperHelper.appendToken(lqw, ImUserLabel::getUniqueToken); + } + + List labels = baseMapper.selectList(lqw); return labels.stream() diff --git a/im-admin/ruoyi-im/src/main/java/org/dromara/im/util/LambdaQueryWrapperHelper.java b/im-admin/ruoyi-im/src/main/java/org/dromara/im/util/LambdaQueryWrapperHelper.java new file mode 100644 index 0000000..d18cb81 --- /dev/null +++ b/im-admin/ruoyi-im/src/main/java/org/dromara/im/util/LambdaQueryWrapperHelper.java @@ -0,0 +1,108 @@ +package org.dromara.im.util; + +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.baomidou.mybatisplus.core.toolkit.support.SFunction; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.dromara.im.service.IImAgentService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * LambdaQueryWrapper 通用工具类 + * + * @author Blue + * @date 2026-04-08 + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class LambdaQueryWrapperHelper { + + private static IImAgentService imAgentService; + + /** + * 初始化服务(由 Spring 调用) + */ + @Component + public static class Initializer { + @Autowired + public void setImAgentService(IImAgentService imAgentService) { + LambdaQueryWrapperHelper.imAgentService = imAgentService; + } + } + + /** + * 为 LambdaQueryWrapper 追加 uniqueToken 查询条件 + *

+ * 根据当前登录用户ID获取对应的 uniqueToken,并添加到查询条件中 + * 如果用户ID为1(超级管理员),则不添加该条件 + * + * @param wrapper 查询条件包装器 + * @param tokenField 实体类中 uniqueToken 字段的 getter 方法引用 + * @param 实体类型 + * @return 添加了 uniqueToken 条件的 LambdaQueryWrapper + */ + public static LambdaQueryWrapper appendToken(LambdaQueryWrapper wrapper, SFunction tokenField) { + if (wrapper == null || tokenField == null) { + return wrapper; + } + + String token = getTokenByUserId(); + + // 如果 token 为 null,表示是超级管理员,不添加过滤条件 + if (token == null) { + return wrapper; + } + + // 添加 uniqueToken 查询条件 + wrapper.eq(ObjectUtil.isNotEmpty(token), tokenField, token); + + return wrapper; + } + + /** + * 为 LambdaUpdateWrapper 追加 uniqueToken 修改/删除条件 + *

+ * 根据当前登录用户ID获取对应的 uniqueToken,并添加到更新或删除的 WHERE 条件中 + * 如果用户ID为1(超级管理员),则不添加该条件 + *

+ * 使用场景: + * - 更新数据时限制只能更新自己的数据 + * - 删除数据时限制只能删除自己的数据 + * + * @param wrapper 更新条件包装器 + * @param tokenField 实体类中 uniqueToken 字段的 getter 方法引用 + * @param 实体类型 + * @return 添加了 uniqueToken 条件的 LambdaUpdateWrapper + */ + public static LambdaUpdateWrapper appendToken(LambdaUpdateWrapper wrapper, SFunction tokenField) { + if (wrapper == null || tokenField == null) { + return wrapper; + } + + String token = getTokenByUserId(); + + // 如果 token 为 null,表示是超级管理员,不添加过滤条件 + if (token == null) { + return wrapper; + } + + // 添加 uniqueToken 修改/删除条件 + wrapper.eq(ObjectUtil.isNotEmpty(token), tokenField, token); + + return wrapper; + } + + /** + * 获取当前用户的 uniqueToken + * + * @return uniqueToken,超级管理员返回 null,未找到返回 "error" + */ + private static String getTokenByUserId() { + if (imAgentService == null) { + throw new IllegalStateException("IImAgentService 未初始化,请确保 Spring 容器已启动"); + } + return imAgentService.getTokenByUserId(); + } +}