Merge branch 'feat/approval-phase1' into feat/approval-t19

This commit is contained in:
2026-10-30 20:18:53 +08:00
13 changed files with 738 additions and 5 deletions
@@ -89,9 +89,12 @@ public class OperationLogAspect {
record.setUrl(request.getRequestURI());
record.setRequestMethod(request.getMethod());
UserAgent ua = UserAgentUtil.parse(ServletUtil.getHeaderIgnoreCase(request, "User-Agent"));
record.setOs(ua.getPlatform().toString());
record.setDevice(ua.getOs().toString());
record.setBrowser(ua.getBrowser().toString());
// 非浏览器客户端可能不带 User-Agent,此时不解析终端信息
if (ua != null) {
record.setOs(ua.getPlatform() == null ? null : ua.getPlatform().toString());
record.setDevice(ua.getOs() == null ? null : ua.getOs().toString());
record.setBrowser(ua.getBrowser() == null ? null : ua.getBrowser().toString());
}
record.setIp(ServletUtil.getClientIP(request));
}
// 记录异常信息
@@ -0,0 +1,60 @@
package com.gxwebsoft.common.system.controller;
import com.gxwebsoft.common.core.annotation.OperationLog;
import com.gxwebsoft.common.core.web.ApiResult;
import com.gxwebsoft.common.core.web.BaseController;
import com.gxwebsoft.common.core.web.PageResult;
import com.gxwebsoft.common.system.param.RoleMemberParam;
import com.gxwebsoft.common.system.service.RoleMemberService;
import com.gxwebsoft.common.system.vo.RoleMemberVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
/**
* 角色成员控制器
*
* @author WebSoft
* @since 2026-10-30
*/
@Api(tags = "角色成员")
@RestController
@RequestMapping("/api/system/role-member")
public class RoleMemberController extends BaseController {
@Resource
private RoleMemberService roleMemberService;
@PreAuthorize("hasAuthority('sys:role:update')")
@ApiOperation("分页查询角色成员")
@GetMapping("/page")
public ApiResult<PageResult<RoleMemberVO>> page(RoleMemberParam param) {
return success(roleMemberService.pageMembers(param));
}
@PreAuthorize("hasAuthority('sys:role:update')")
@OperationLog
@ApiOperation("添加角色成员")
@PostMapping()
public ApiResult<?> save(@RequestBody RoleMemberParam param) {
roleMemberService.addMember(param.getRoleId(), param.getUserId());
return success("添加成功");
}
@PreAuthorize("hasAuthority('sys:role:update')")
@OperationLog
@ApiOperation("移除角色成员")
@DeleteMapping()
public ApiResult<?> remove(@RequestParam Integer roleId, @RequestParam Integer userId) {
roleMemberService.removeMember(roleId, userId);
return success("移除成功");
}
}
@@ -0,0 +1,29 @@
package com.gxwebsoft.common.system.mapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.gxwebsoft.common.system.vo.RoleMemberVO;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 角色成员Mapper
*
* @author WebSoft
* @since 2026-10-30
*/
public interface RoleMemberMapper {
/**
* 分页查询角色下的启用用户
*
* @param page 分页对象
* @param roleId 角色id
* @param keywords 账号/姓名关键词
* @return List<RoleMemberVO>
*/
List<RoleMemberVO> selectPageByRoleId(@Param("page") IPage<RoleMemberVO> page,
@Param("roleId") Integer roleId,
@Param("keywords") String keywords);
}
@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.gxwebsoft.common.system.mapper.RoleMemberMapper">
<!-- 角色下的启用用户 -->
<select id="selectPageByRoleId" resultType="com.gxwebsoft.common.system.vo.RoleMemberVO">
SELECT a.user_id,
a.username,
a.real_name,
a.nickname,
a.organization_id,
a.status,
c.organization_name
FROM sys_user_role b
INNER JOIN sys_user a ON a.user_id = b.user_id AND a.deleted = 0 AND a.status = 0
LEFT JOIN sys_organization c ON c.organization_id = a.organization_id AND c.deleted = 0
WHERE b.role_id = #{roleId}
<if test="keywords != null and keywords != ''">
AND (a.username LIKE CONCAT('%', #{keywords}, '%')
OR a.real_name LIKE CONCAT('%', #{keywords}, '%')
OR a.nickname LIKE CONCAT('%', #{keywords}, '%'))
</if>
ORDER BY a.user_id DESC
</select>
</mapper>
@@ -0,0 +1,31 @@
package com.gxwebsoft.common.system.param;
import com.baomidou.mybatisplus.annotation.TableField;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.gxwebsoft.common.core.web.BaseParam;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 角色成员查询/操作参数
*
* @author WebSoft
* @since 2026-10-30
*/
@Data
@EqualsAndHashCode(callSuper = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
@ApiModel(description = "角色成员参数")
public class RoleMemberParam extends BaseParam {
private static final long serialVersionUID = 1L;
@ApiModelProperty("角色id")
@TableField(exist = false)
private Integer roleId;
@ApiModelProperty("用户id")
@TableField(exist = false)
private Integer userId;
}
@@ -0,0 +1,38 @@
package com.gxwebsoft.common.system.service;
import com.gxwebsoft.common.core.web.PageResult;
import com.gxwebsoft.common.system.param.RoleMemberParam;
import com.gxwebsoft.common.system.vo.RoleMemberVO;
/**
* 角色成员Service
*
* @author WebSoft
* @since 2026-10-30
*/
public interface RoleMemberService {
/**
* 分页查询角色下的启用用户
*
* @param param 查询参数
* @return PageResult<RoleMemberVO>
*/
PageResult<RoleMemberVO> pageMembers(RoleMemberParam param);
/**
* 添加角色成员
*
* @param roleId 角色id
* @param userId 用户id
*/
void addMember(Integer roleId, Integer userId);
/**
* 移除角色成员
*
* @param roleId 角色id
* @param userId 用户id
*/
void removeMember(Integer roleId, Integer userId);
}
@@ -1,5 +1,6 @@
package com.gxwebsoft.common.system.service.impl;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.gxwebsoft.common.core.web.PageParam;
import com.gxwebsoft.common.core.web.PageResult;
@@ -19,6 +20,7 @@ import java.util.List;
* @since 2018-12-24 16:10:02
*/
@Service
@DS("server")
public class OperationRecordServiceImpl extends ServiceImpl<OperationRecordMapper, OperationRecord>
implements OperationRecordService {
@@ -0,0 +1,102 @@
package com.gxwebsoft.common.system.service.impl;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.gxwebsoft.common.core.exception.BusinessException;
import com.gxwebsoft.common.core.web.PageParam;
import com.gxwebsoft.common.core.web.PageResult;
import com.gxwebsoft.common.system.entity.Role;
import com.gxwebsoft.common.system.entity.User;
import com.gxwebsoft.common.system.entity.UserRole;
import com.gxwebsoft.common.system.mapper.RoleMapper;
import com.gxwebsoft.common.system.mapper.RoleMemberMapper;
import com.gxwebsoft.common.system.mapper.UserMapper;
import com.gxwebsoft.common.system.mapper.UserRoleMapper;
import com.gxwebsoft.common.system.param.RoleMemberParam;
import com.gxwebsoft.common.system.service.RoleMemberService;
import com.gxwebsoft.common.system.vo.RoleMemberVO;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.Collections;
import java.util.List;
/**
* 角色成员Service实现
*
* @author WebSoft
* @since 2026-10-30
*/
@Service
@DS("server")
public class RoleMemberServiceImpl implements RoleMemberService {
@Resource
private RoleMapper roleMapper;
@Resource
private UserMapper userMapper;
@Resource
private UserRoleMapper userRoleMapper;
@Resource
private RoleMemberMapper roleMemberMapper;
@Override
public PageResult<RoleMemberVO> pageMembers(RoleMemberParam param) {
PageParam<RoleMemberVO, RoleMemberParam> page = new PageParam<>(param);
List<RoleMemberVO> list = roleMemberMapper.selectPageByRoleId(page,
param.getRoleId(), param.getKeywords());
return new PageResult<>(list, page.getTotal());
}
@Transactional(rollbackFor = Exception.class)
@Override
public void addMember(Integer roleId, Integer userId) {
requireIds(roleId, userId);
checkRoleExists(roleId);
User user = userMapper.selectById(userId);
if (user == null) {
throw new BusinessException("用户不存在");
}
if (user.getStatus() == null || user.getStatus() != 0) {
throw new BusinessException("该用户已停用,不能添加为角色成员");
}
if (userRoleMapper.selectCount(new QueryWrapper<UserRole>()
.eq("user_id", userId)
.eq("role_id", roleId)) > 0) {
throw new BusinessException("该用户已是角色成员");
}
if (userRoleMapper.insertBatch(userId, Collections.singletonList(roleId)) < 1) {
throw new BusinessException("添加成员失败");
}
}
@Transactional(rollbackFor = Exception.class)
@Override
public void removeMember(Integer roleId, Integer userId) {
requireIds(roleId, userId);
checkRoleExists(roleId);
int removed = userRoleMapper.delete(new LambdaQueryWrapper<UserRole>()
.eq(UserRole::getUserId, userId)
.eq(UserRole::getRoleId, roleId));
if (removed < 1) {
throw new BusinessException("该用户不是该角色成员");
}
}
private void requireIds(Integer roleId, Integer userId) {
if (roleId == null) {
throw new BusinessException("角色不能为空");
}
if (userId == null) {
throw new BusinessException("用户不能为空");
}
}
private void checkRoleExists(Integer roleId) {
Role role = roleMapper.selectById(roleId);
if (role == null) {
throw new BusinessException("角色不存在");
}
}
}
@@ -49,6 +49,7 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
@Resource
private RedisUtil redisUtil;
@DS("server")
@Override
public PageResult<User> pageRel(UserParam param) {
PageParam<User, UserParam> page = new PageParam<>(param);
@@ -142,6 +143,7 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
return result;
}
@DS("server")
@Transactional(rollbackFor = {Exception.class})
@Override
public boolean updateUser(User user) {
@@ -161,10 +163,11 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
throw new BusinessException("邮箱已存在");
}
boolean result = baseMapper.updateById(user) > 0;
if (result && user.getRoles() != null && user.getRoles().size() > 0) {
// roles 非 null 即代表本次提交了角色数据:先清空旧关联,空列表表示清空全部角色
if (result && user.getRoles() != null) {
userRoleService.remove(new LambdaUpdateWrapper<UserRole>().eq(UserRole::getUserId, user.getUserId()));
List<Integer> roleIds = user.getRoles().stream().map(Role::getRoleId).collect(Collectors.toList());
if (userRoleService.saveBatch(user.getUserId(), roleIds) < roleIds.size()) {
if (!roleIds.isEmpty() && userRoleService.saveBatch(user.getUserId(), roleIds) < roleIds.size()) {
throw new BusinessException("用户角色添加失败");
}
}
@@ -0,0 +1,40 @@
package com.gxwebsoft.common.system.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
/**
* 角色成员
*
* @author WebSoft
* @since 2026-10-30
*/
@Data
@ApiModel(description = "角色成员")
public class RoleMemberVO implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty("用户id")
private Integer userId;
@ApiModelProperty("账号")
private String username;
@ApiModelProperty("真实姓名")
private String realName;
@ApiModelProperty("昵称")
private String nickname;
@ApiModelProperty("所属组织id")
private Integer organizationId;
@ApiModelProperty("所属组织名称")
private String organizationName;
@ApiModelProperty("状态, 0正常, 1停用")
private Integer status;
}