diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenant/vo/tenant/TenantSaveReqVO.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenant/vo/tenant/TenantSaveReqVO.java index 073268d..a11e04b 100644 --- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenant/vo/tenant/TenantSaveReqVO.java +++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/tenant/vo/tenant/TenantSaveReqVO.java @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.system.controller.admin.tenant.vo.tenant; import cn.hutool.core.util.ObjectUtil; import com.fasterxml.jackson.annotation.JsonIgnore; +import com.mzt.logapi.starter.annotation.DiffLogField; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import org.hibernate.validator.constraints.Length; @@ -24,41 +25,52 @@ public class TenantSaveReqVO { @Schema(description = "租户名", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道") @NotNull(message = "租户名不能为空") + @DiffLogField(name = "租户名") private String name; @Schema(description = "联系人", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿") @NotNull(message = "联系人不能为空") + @DiffLogField(name = "联系人") private String contactName; @Schema(description = "联系手机", example = "15601691300") + @DiffLogField(name = "联系手机") private String contactMobile; @Schema(description = "租户状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") @NotNull(message = "租户状态") + @DiffLogField(name = "租户状态") private Integer status; @Schema(description = "绑定域名", example = "https://www.iocoder.cn") + @DiffLogField(name = "绑定域名") private String website; @Schema(description = "租户套餐编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") @NotNull(message = "租户套餐编号不能为空") + @DiffLogField(name = "租户套餐编号") private Long packageId; @Schema(description = "过期时间", requiredMode = Schema.RequiredMode.REQUIRED) @NotNull(message = "过期时间不能为空") + @DiffLogField(name = "过期时间") private LocalDateTime expireTime; @Schema(description = "ai过期时间") + @DiffLogField(name = "AI 过期时间") private LocalDateTime aiExpireTime; @Schema(description = "大哥过期时间") + @DiffLogField(name = "大哥过期时间") private LocalDateTime brotherExpireTime; @Schema(description = "爬主播到期时间") + @DiffLogField(name = "爬主播到期时间") private LocalDateTime crawlExpireTime; @Schema(description = "账号数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") @NotNull(message = "账号数量不能为空") + @DiffLogField(name = "账号数量") private Integer accountCount; // ========== 仅【创建】时,需要传递的字段 ========== @@ -66,6 +78,7 @@ public class TenantSaveReqVO { @Schema(description = "用户账号", requiredMode = Schema.RequiredMode.REQUIRED, example = "yudao") @Pattern(regexp = "^[a-zA-Z0-9]{4,30}$", message = "用户账号由 数字、字母 组成") @Size(min = 4, max = 30, message = "用户账号长度为 4-30 个字符") + @DiffLogField(name = "管理员账号") private String username; @Schema(description = "密码", requiredMode = Schema.RequiredMode.REQUIRED, example = "123456") @@ -80,19 +93,24 @@ public class TenantSaveReqVO { } @Schema(description = "是否允许登录爬虫客户端", example = "0不允许,1允许") + @DiffLogField(name = "允许登录爬虫客户端") private Byte crawl; @Schema(description = "是否允许登录爬虫客户端", example = "0不允许,1允许") + @DiffLogField(name = "允许登录大哥客户端") private Byte bigBrother; @Schema(description = "能否登录 AI 聊天工具", example = "0不允许,1允许") + @DiffLogField(name = "允许登录 AI 聊天工具") private Byte aiChat; @Schema(description = "备注", example = "备注") + @DiffLogField(name = "备注") private String remark; @Schema(description = "租户类型", example = "代理/客户") @NotNull(message = "租户类型不能为空") + @DiffLogField(name = "租户类型") private String tenantType; } diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/UserController.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/UserController.java index 1d7f448..a671c94 100644 --- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/UserController.java +++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/UserController.java @@ -194,7 +194,6 @@ public class UserController { } - @TenantIgnore @PutMapping("update-client-role") @Operation(summary = "修改用户客户端使用权限") @PreAuthorize("@ss.hasPermission('system:user:update-client')") diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/user/UserClientSaveReqVO.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/user/UserClientSaveReqVO.java index bbb4fdd..8f17fbf 100644 --- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/user/UserClientSaveReqVO.java +++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/user/UserClientSaveReqVO.java @@ -1,42 +1,42 @@ package cn.iocoder.yudao.module.system.controller.admin.user.vo.user; -import cn.hutool.core.util.ObjectUtil; -import cn.iocoder.yudao.framework.common.validation.Mobile; -import cn.iocoder.yudao.module.system.framework.operatelog.core.DeptParseFunction; -import cn.iocoder.yudao.module.system.framework.operatelog.core.PostParseFunction; -import cn.iocoder.yudao.module.system.framework.operatelog.core.SexParseFunction; -import com.fasterxml.jackson.annotation.JsonIgnore; +import cn.iocoder.yudao.module.system.framework.operatelog.core.BooleanParseFunction; import com.mzt.logapi.starter.annotation.DiffLogField; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; -import org.hibernate.validator.constraints.Length; -import javax.validation.constraints.*; -import java.util.Set; +import javax.validation.constraints.NotNull; @Schema(description = "管理后台 - 用户创建客户端用户/修改 Request VO") @Data public class UserClientSaveReqVO { @Schema(description = "用户编号", example = "1024") + @NotNull(message = "用户编号不能为空") private Long id; @Schema(description = "是否允许登录主播爬虫客户端", example = "0不允许,1允许") + @DiffLogField(name = "允许登录主播爬虫客户端", function = BooleanParseFunction.NAME) private Byte crawl; @Schema(description = "是否允许登录大哥爬虫客户端", example = "0不允许,1允许") + @DiffLogField(name = "允许登录大哥爬虫客户端", function = BooleanParseFunction.NAME) private Byte bigBrother; @Schema(description = "租户 Id", example = "1") + @DiffLogField(name = "租户编号") private Long tenantId; @Schema(description = "能否登录 AI 聊天工具", example = "0不允许,1允许") + @DiffLogField(name = "允许登录 AI 聊天工具", function = BooleanParseFunction.NAME) private Byte aiChat; @Schema(description = "是否允许使用 AI 回复", example = "0不允许,1允许") + @DiffLogField(name = "允许使用 AI 回复", function = BooleanParseFunction.NAME) private Byte aiReplay; @Schema(description = "是否允许登录 Web AI 客户端", example = "0不允许,1允许") + @DiffLogField(name = "允许登录 Web AI 客户端", function = BooleanParseFunction.NAME) private Byte webAi; } diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/enums/LogRecordConstants.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/enums/LogRecordConstants.java index 4ee3840..894f400 100644 --- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/enums/LogRecordConstants.java +++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/enums/LogRecordConstants.java @@ -19,6 +19,8 @@ public interface LogRecordConstants { String SYSTEM_USER_DELETE_SUCCESS = "删除了用户【{{#user.nickname}}】"; String SYSTEM_USER_UPDATE_PASSWORD_SUB_TYPE = "重置用户密码"; String SYSTEM_USER_UPDATE_PASSWORD_SUCCESS = "将用户【{{#user.nickname}}】的密码从【{{#user.password}}】重置为【{{#newPassword}}】"; + String SYSTEM_USER_UPDATE_CLIENT_ROLE_SUB_TYPE = "更新用户客户端权限"; + String SYSTEM_USER_UPDATE_CLIENT_ROLE_SUCCESS = "更新了用户【{{#user.username}}】客户端权限{{#clientRoleChangeDetail}}"; // ======================= SYSTEM_ROLE 角色 ======================= @@ -30,4 +32,12 @@ public interface LogRecordConstants { String SYSTEM_ROLE_DELETE_SUB_TYPE = "删除角色"; String SYSTEM_ROLE_DELETE_SUCCESS = "删除了角色【{{#role.name}}】"; + // ======================= SYSTEM_TENANT 租户 ======================= + + String SYSTEM_TENANT_TYPE = "SYSTEM 租户"; + String SYSTEM_TENANT_CREATE_SUB_TYPE = "创建租户"; + String SYSTEM_TENANT_CREATE_SUCCESS = "创建了租户【{{#tenant.name}}】: {_DIFF{#createReqVO}}"; + String SYSTEM_TENANT_UPDATE_SUB_TYPE = "更新租户"; + String SYSTEM_TENANT_UPDATE_SUCCESS = "更新了租户【{{#tenant.name}}】: {_DIFF{#updateReqVO}} {{#tenantChangeDetail}}"; + } diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/tenant/TenantServiceImpl.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/tenant/TenantServiceImpl.java index 175ba66..b4d1656 100755 --- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/tenant/TenantServiceImpl.java +++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/tenant/TenantServiceImpl.java @@ -52,6 +52,9 @@ import cn.iocoder.yudao.module.system.service.tenantagencypackage.TenantAgencyPa import cn.iocoder.yudao.module.system.service.tenantbalance.TenantBalanceService; import cn.iocoder.yudao.module.system.service.user.AdminUserService; import com.baomidou.dynamic.datasource.annotation.DSTransactional; +import com.mzt.logapi.context.LogRecordContext; +import com.mzt.logapi.service.impl.DiffParseFunction; +import com.mzt.logapi.starter.annotation.LogRecord; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; @@ -64,6 +67,7 @@ import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; import java.time.LocalDateTime; import java.time.temporal.ChronoUnit; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -73,6 +77,7 @@ import java.util.concurrent.atomic.AtomicReference; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*; +import static cn.iocoder.yudao.module.system.enums.LogRecordConstants.*; import static java.util.Collections.singleton; /** @@ -299,6 +304,8 @@ public class TenantServiceImpl implements TenantService { @Override @DSTransactional // 多数据源,使用 @DSTransactional 保证本地事务,以及数据源的切换 @DataPermission(enable = false) // 参见 https://gitee.com/zhijiantianya/ruoyi-vue-pro/pulls/1154 说明 + @LogRecord(type = SYSTEM_TENANT_TYPE, subType = SYSTEM_TENANT_CREATE_SUB_TYPE, bizNo = "{{#tenant.id}}", + success = SYSTEM_TENANT_CREATE_SUCCESS) public Long createTenant(TenantSaveReqVO createReqVO) { // 获取当前操作租户的ID(即创建者的租户ID) @@ -410,6 +417,8 @@ public class TenantServiceImpl implements TenantService { tenantBalanceMapper.insert(tenantBalance); // 插入钱包记录 } + LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, new TenantSaveReqVO()); + LogRecordContext.putVariable("tenant", tenant); // 返回新创建的租户ID return tenant.getId(); } @@ -466,6 +475,8 @@ public class TenantServiceImpl implements TenantService { tenantBalanceMapper.insert(tenantBalance); // 插入钱包记录 } + LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, new TenantSaveReqVO()); + LogRecordContext.putVariable("tenant", tenant); // 返回新创建的租户ID return tenant.getId(); } @@ -502,6 +513,8 @@ public class TenantServiceImpl implements TenantService { @Override @DSTransactional // 多数据源,使用 @DSTransactional 保证本地事务,以及数据源的切换 + @LogRecord(type = SYSTEM_TENANT_TYPE, subType = SYSTEM_TENANT_UPDATE_SUB_TYPE, bizNo = "{{#updateReqVO.id}}", + success = SYSTEM_TENANT_UPDATE_SUCCESS) public void updateTenant(TenantSaveReqVO updateReqVO) { // 校验存在 TenantDO tenant = validateUpdateTenant(updateReqVO.getId()); @@ -531,8 +544,12 @@ public class TenantServiceImpl implements TenantService { } } + LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(tenant, TenantSaveReqVO.class)); + LogRecordContext.putVariable("tenant", tenant); + } + private void validTenantNameDuplicate(String name, Long id) { TenantDO tenant = tenantMapper.selectByName(name); if (tenant == null) { diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java index cf17221..89b6f5c 100644 --- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java +++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java @@ -13,7 +13,7 @@ import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils; import cn.iocoder.yudao.framework.datapermission.core.util.DataPermissionUtils; -import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore; +import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils; import cn.iocoder.yudao.module.infra.api.config.ConfigApi; import cn.iocoder.yudao.module.system.controller.admin.auth.vo.AuthRegisterReqVO; import cn.iocoder.yudao.module.system.controller.admin.user.vo.profile.UserProfileUpdatePasswordReqVO; @@ -547,10 +547,74 @@ public class AdminUserServiceImpl implements AdminUserService { } @Override + @LogRecord(type = SYSTEM_USER_TYPE, subType = SYSTEM_USER_UPDATE_CLIENT_ROLE_SUB_TYPE, bizNo = "{{#user.id}}", + success = SYSTEM_USER_UPDATE_CLIENT_ROLE_SUCCESS) public void updateUserWithClientRole(UserClientSaveReqVO reqVO) { + // 1. 校验用户存在 + AdminUserDO oldUser = TenantUtils.executeIgnore(() -> validateUserExists(reqVO.getId())); + fillUserClientRoleReqIfAbsent(reqVO, oldUser); + LogRecordContext.putVariable("user", oldUser); + LogRecordContext.putVariable("clientRoleChangeDetail", buildClientRoleChangeDetail(oldUser, reqVO)); + // 2.1 更新用户 AdminUserDO updateObj = BeanUtils.toBean(reqVO, AdminUserDO.class); - userMapper.updateById(updateObj); + TenantUtils.executeIgnore(() -> userMapper.updateById(updateObj)); + + // 3. 记录操作日志上下文 + LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldUser, UserClientSaveReqVO.class)); + } + + private void fillUserClientRoleReqIfAbsent(UserClientSaveReqVO reqVO, AdminUserDO oldUser) { + if (reqVO.getTenantId() == null) { + reqVO.setTenantId(oldUser.getTenantId()); + } + if (reqVO.getCrawl() == null) { + reqVO.setCrawl(oldUser.getCrawl()); + } + if (reqVO.getBigBrother() == null) { + reqVO.setBigBrother(oldUser.getBigBrother()); + } + if (reqVO.getAiChat() == null) { + reqVO.setAiChat(oldUser.getAiChat()); + } + if (reqVO.getAiReplay() == null) { + reqVO.setAiReplay(oldUser.getAiReplay()); + } + if (reqVO.getWebAi() == null) { + reqVO.setWebAi(oldUser.getWebAi()); + } + } + + private String buildClientRoleChangeDetail(AdminUserDO oldUser, UserClientSaveReqVO reqVO) { + List changes = new ArrayList<>(); + appendClientRoleChange(changes, "允许登录主播爬虫客户端", oldUser.getCrawl(), reqVO.getCrawl()); + appendClientRoleChange(changes, "允许登录大哥爬虫客户端", oldUser.getBigBrother(), reqVO.getBigBrother()); + appendClientRoleChange(changes, "允许登录 AI 聊天工具", oldUser.getAiChat(), reqVO.getAiChat()); + appendClientRoleChange(changes, "允许使用 AI 回复", oldUser.getAiReplay(), reqVO.getAiReplay()); + appendClientRoleChange(changes, "允许登录 Web AI 客户端", oldUser.getWebAi(), reqVO.getWebAi()); + if (changes.isEmpty()) { + return "(未发现字段变更)"; + } + return "(变更明细:" + String.join(";", changes) + ")"; + } + + private void appendClientRoleChange(List changes, String fieldName, Byte oldValue, Byte newValue) { + if (ObjUtil.notEqual(oldValue, newValue)) { + changes.add(fieldName + ":【" + formatClientRoleValue(oldValue) + "】->【" + formatClientRoleValue(newValue) + "】"); + } + } + + private String formatClientRoleValue(Byte value) { + if (value == null) { + return "-"; + } + if (value == 1) { + return "允许"; + } + if (value == 0) { + return "不允许"; + } + return String.valueOf(value); } @Override