This commit is contained in:
2025-04-19 17:31:40 +08:00
parent 6230c36cf2
commit e731ef8bcd
146 changed files with 6286 additions and 2 deletions

View File

@@ -0,0 +1,18 @@
package com.tashow.cloud.tashowmoduleuserbiz;
import cn.dev33.satoken.sso.SaSsoManager;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class TashowModuleUserBizApplication {
public static void main(String[] args) {
SpringApplication.run(TashowModuleUserBizApplication.class, args);
System.out.println();
System.out.println("---------------------- Sa-Token SSO 统一认证中心启动成功 ----------------------");
System.out.println("配置信息:" + SaSsoManager.getServerConfig());
System.out.println();
}
}

View File

@@ -0,0 +1,27 @@
package com.tashow.cloud.tashowmoduleuserbiz.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* MyBatis Plus配置类
*/
@Configuration
@MapperScan("com.tashow.cloud.tashowmoduleuserbiz.mapper")
public class MybatisPlusConfig {
/**
* 配置分页插件
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 添加分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}

View File

@@ -0,0 +1,203 @@
package com.tashow.cloud.tashowmoduleuserbiz.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.annotation.SaIgnore;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
import com.tashow.cloud.model.SystemRole;
import com.tashow.cloud.service.SystemRoleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
import java.util.HashMap;
import java.util.stream.Collectors;
/**
* 角色管理控制器
*/
@RestController
@RequestMapping("/role")
public class RoleController {
@Autowired
private SystemRoleService roleService;
/**
* 创建角色
*/
@PostMapping
@SaCheckPermission("system:role:create")
public SaResult createRole(@RequestBody Map<String, Object> params) {
try {
SystemRole role = new SystemRole();
role.setName((String) params.get("name"));
role.setCode((String) params.get("code"));
role.setSort((Integer) params.get("sort"));
role.setStatus((Integer) params.get("status"));
role.setType((Integer) params.get("type"));
role.setRemark((String) params.get("remark"));
role.setDataScope((Integer) params.get("dataScope"));
role.setDataScopeDeptIds((String) params.get("dataScopeDeptIds"));
// 设置创建者
String username = StpUtil.getLoginIdAsString();
role.setCreator(username);
role.setUpdater(username);
@SuppressWarnings("unchecked")
List<Long> menuIds = (List<Long>) params.get("menuIds");
boolean success = roleService.createRole(role, menuIds);
if (success) {
return SaResult.ok("角色创建成功").setData(role);
} else {
return SaResult.error("角色创建失败");
}
} catch (Exception e) {
return SaResult.error("角色创建异常: " + e.getMessage());
}
}
/**
* 更新角色
*/
@PutMapping("/{id}")
@SaCheckPermission("system:role:update")
public SaResult updateRole(@PathVariable("id") Long id, @RequestBody Map<String, Object> params) {
try {
SystemRole role = roleService.getRoleById(id);
if (role == null) {
return SaResult.error("角色不存在");
}
role.setName((String) params.get("name"));
role.setCode((String) params.get("code"));
role.setSort((Integer) params.get("sort"));
role.setStatus((Integer) params.get("status"));
role.setType((Integer) params.get("type"));
role.setRemark((String) params.get("remark"));
role.setDataScope((Integer) params.get("dataScope"));
role.setDataScopeDeptIds((String) params.get("dataScopeDeptIds"));
// 设置更新者
String username = StpUtil.getLoginIdAsString();
role.setUpdater(username);
@SuppressWarnings("unchecked")
List<Long> menuIds = (List<Long>) params.get("menuIds");
boolean success = roleService.updateRole(role, menuIds);
if (success) {
return SaResult.ok("角色更新成功");
} else {
return SaResult.error("角色更新失败");
}
} catch (Exception e) {
return SaResult.error("角色更新异常: " + e.getMessage());
}
}
/**
* 删除角色
*/
@DeleteMapping("/{id}")
@SaCheckPermission("system:role:delete")
public SaResult deleteRole(@PathVariable("id") Long id) {
try {
boolean success = roleService.deleteRole(id);
if (success) {
return SaResult.ok("角色删除成功");
} else {
return SaResult.error("角色删除失败");
}
} catch (Exception e) {
return SaResult.error("角色删除异常: " + e.getMessage());
}
}
/**
* 获取角色详情
*/
@GetMapping("/{id}")
@SaCheckPermission("system:role:query")
public SaResult getRoleDetail(@PathVariable("id") Long id) {
try {
SystemRole role = roleService.getRoleById(id);
if (role == null) {
return SaResult.error("角色不存在");
}
Map<String, Object> result = new HashMap<>();
result.put("role", role);
// 获取角色关联的菜单ID列表
List<Long> menuIds = roleService.getRoleMenuIds(id);
result.put("menuIds", menuIds);
return SaResult.ok().setData(result);
} catch (Exception e) {
return SaResult.error("获取角色详情异常: " + e.getMessage());
}
}
/**
* 获取角色列表
*/
@GetMapping("/list")
@SaCheckPermission("system:role:query")
public SaResult getRoleList() {
try {
List<SystemRole> roles = roleService.getAllRoles();
return SaResult.ok().setData(roles);
} catch (Exception e) {
return SaResult.error("获取角色列表异常: " + e.getMessage());
}
}
/**
* 获取启用状态的角色列表(可用于下拉选择)
*/
@GetMapping("/options")
@SaIgnore
public SaResult getRoleOptions() {
try {
List<SystemRole> roles = roleService.getRolesByStatus(0); // 0表示正常状态
List<Map<String, Object>> options = roles.stream()
.map(role -> {
Map<String, Object> option = new HashMap<>();
option.put("value", role.getId());
option.put("label", role.getName());
option.put("code", role.getCode());
return option;
})
.collect(Collectors.toList());
return SaResult.ok().setData(options);
} catch (Exception e) {
return SaResult.error("获取角色选项异常: " + e.getMessage());
}
}
/**
* 更新角色状态
*/
@PutMapping("/{id}/status")
@SaCheckPermission("system:role:update")
public SaResult updateRoleStatus(@PathVariable("id") Long id, @RequestParam("status") Integer status) {
try {
boolean success = roleService.updateRoleStatus(id, status);
if (success) {
return SaResult.ok("角色状态更新成功");
} else {
return SaResult.error("角色状态更新失败");
}
} catch (Exception e) {
return SaResult.error("角色状态更新异常: " + e.getMessage());
}
}
}

View File

@@ -0,0 +1,310 @@
package com.tashow.cloud.tashowmoduleuserbiz.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.annotation.SaIgnore;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
import com.tashow.cloud.model.SystemUser;
import com.tashow.cloud.service.SystemUserService;
import com.tashow.cloud.model.dto.UserRegisterReqDTO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.Valid;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 用户管理控制器
*/
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private SystemUserService userService;
/**
* 用户注册
*/
@SaIgnore
@PostMapping("/register")
public SaResult register(@Valid @RequestBody UserRegisterReqDTO reqDTO, HttpServletRequest request) {
// 检查用户名是否已存在
SystemUser existingUser = userService.getUserByUsername(reqDTO.getUsername());
if (existingUser != null) {
return SaResult.error("用户名已被使用");
}
// 加密密码
String encodedPassword = userService.encodePassword(reqDTO.getPassword());
// 创建用户
SystemUser user = new SystemUser();
user.setUsername(reqDTO.getUsername());
user.setPassword(encodedPassword);
user.setNickname(reqDTO.getNickname());
user.setEmail(reqDTO.getEmail());
user.setMobile(reqDTO.getMobile());
user.setSex(reqDTO.getSex());
user.setStatus(0); // 正常状态
// 设置登录IP
String loginIp = reqDTO.getLoginIp();
if (loginIp == null || loginIp.isEmpty()) {
loginIp = getClientIp(request);
}
user.setLoginIp(loginIp);
// 注册用户
boolean success = userService.registerUser(user);
if (success) {
return SaResult.ok("注册成功");
} else {
return SaResult.error("注册失败,请稍后重试");
}
}
/**
* 创建用户
*/
@PostMapping
@SaCheckPermission("system:user:create")
public SaResult createUser(@RequestBody Map<String, Object> params) {
try {
SystemUser user = new SystemUser();
user.setUsername((String) params.get("username"));
user.setPassword((String) params.get("password")); // 会在service中加密
user.setNickname((String) params.get("nickname"));
user.setEmail((String) params.get("email"));
user.setMobile((String) params.get("mobile"));
user.setRemark((String) params.get("remark"));
user.setStatus((Integer) params.get("status"));
user.setSex((Integer) params.get("sex"));
user.setDeptId((Long) params.get("deptId"));
user.setAvatar((String) params.get("avatar"));
// 设置创建者
String username = StpUtil.getLoginIdAsString();
user.setCreator(username);
user.setUpdater(username);
@SuppressWarnings("unchecked")
List<Long> roleIds = (List<Long>) params.get("roleIds");
@SuppressWarnings("unchecked")
List<Long> postIds = (List<Long>) params.get("postIds");
boolean success = userService.createUser(user, roleIds, postIds);
if (success) {
return SaResult.ok("用户创建成功").setData(user);
} else {
return SaResult.error("用户创建失败");
}
} catch (Exception e) {
return SaResult.error("用户创建异常: " + e.getMessage());
}
}
/**
* 更新用户
*/
@PutMapping("/{id}")
@SaCheckPermission("system:user:update")
public SaResult updateUser(@PathVariable("id") Long id, @RequestBody Map<String, Object> params) {
try {
SystemUser user = userService.getUserById(id);
if (user == null) {
return SaResult.error("用户不存在");
}
user.setNickname((String) params.get("nickname"));
user.setEmail((String) params.get("email"));
user.setMobile((String) params.get("mobile"));
user.setRemark((String) params.get("remark"));
user.setStatus((Integer) params.get("status"));
user.setSex((Integer) params.get("sex"));
user.setDeptId((Long) params.get("deptId"));
user.setAvatar((String) params.get("avatar"));
// 设置更新者
String username = StpUtil.getLoginIdAsString();
user.setUpdater(username);
@SuppressWarnings("unchecked")
List<Long> roleIds = (List<Long>) params.get("roleIds");
@SuppressWarnings("unchecked")
List<Long> postIds = (List<Long>) params.get("postIds");
boolean success = userService.updateUser(user, roleIds, postIds);
if (success) {
return SaResult.ok("用户更新成功");
} else {
return SaResult.error("用户更新失败");
}
} catch (Exception e) {
return SaResult.error("用户更新异常: " + e.getMessage());
}
}
/**
* 删除用户
*/
@DeleteMapping("/{id}")
@SaCheckPermission("system:user:delete")
public SaResult deleteUser(@PathVariable("id") Long id) {
try {
// 判断是否为当前登录用户
Long loginId = StpUtil.getLoginIdAsLong();
if (loginId.equals(id)) {
return SaResult.error("不能删除当前登录用户");
}
boolean success = userService.deleteUser(id);
if (success) {
return SaResult.ok("用户删除成功");
} else {
return SaResult.error("用户删除失败");
}
} catch (Exception e) {
return SaResult.error("用户删除异常: " + e.getMessage());
}
}
/**
* 获取用户详情
*/
@GetMapping("/{id}")
@SaCheckPermission("system:user:query")
public SaResult getUserDetail(@PathVariable("id") Long id) {
try {
SystemUser user = userService.getUserById(id);
if (user == null) {
return SaResult.error("用户不存在");
}
// 脱敏处理
user.setPassword(null);
Map<String, Object> result = new HashMap<>();
result.put("user", user);
// 获取用户角色ID列表
List<Long> roleIds = userService.getUserRoleIds(id);
result.put("roleIds", roleIds);
// 获取用户岗位ID列表
List<Long> postIds = userService.getUserPostIds(id);
result.put("postIds", postIds);
return SaResult.ok().setData(result);
} catch (Exception e) {
return SaResult.error("获取用户详情异常: " + e.getMessage());
}
}
/**
* 获取当前登录用户信息
*/
@GetMapping("/info")
public SaResult getCurrentUserInfo() {
try {
Long userId = StpUtil.getLoginIdAsLong();
SystemUser user = userService.getUserById(userId);
if (user == null) {
return SaResult.error("用户不存在");
}
// 脱敏处理
user.setPassword(null);
Map<String, Object> result = new HashMap<>();
result.put("user", user);
// 获取用户角色信息
List<String> roleCodes = userService.getUserRoleCodes(userId);
List<String> roleNames = userService.getUserRoleNames(userId);
result.put("roles", roleCodes);
result.put("roleNames", roleNames);
return SaResult.ok().setData(result);
} catch (Exception e) {
return SaResult.error("获取当前用户信息异常: " + e.getMessage());
}
}
/**
* 重置用户密码
*/
@PutMapping("/{id}/password")
@SaCheckPermission("system:user:update")
public SaResult resetPassword(@PathVariable("id") Long id, @RequestParam("newPassword") String newPassword) {
try {
boolean success = userService.resetUserPassword(id, newPassword);
if (success) {
return SaResult.ok("密码重置成功");
} else {
return SaResult.error("密码重置失败");
}
} catch (Exception e) {
return SaResult.error("密码重置异常: " + e.getMessage());
}
}
/**
* 更新用户状态
*/
@PutMapping("/{id}/status")
@SaCheckPermission("system:user:update")
public SaResult updateUserStatus(@PathVariable("id") Long id, @RequestParam("status") Integer status) {
try {
// 判断是否为当前登录用户
Long loginId = StpUtil.getLoginIdAsLong();
if (loginId.equals(id)) {
return SaResult.error("不能修改当前登录用户的状态");
}
boolean success = userService.updateUserStatus(id, status);
if (success) {
return SaResult.ok("用户状态更新成功");
} else {
return SaResult.error("用户状态更新失败");
}
} catch (Exception e) {
return SaResult.error("用户状态更新异常: " + e.getMessage());
}
}
/**
* 获取客户端真实IP地址
*/
private String getClientIp(HttpServletRequest request) {
String ip = request.getHeader("X-Forwarded-For");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("X-Real-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
// 多个代理的情况第一个IP为客户端真实IP
if (ip != null && ip.indexOf(",") > 0) {
ip = ip.substring(0, ip.indexOf(","));
}
return ip;
}
}

View File

@@ -0,0 +1,13 @@
package com.tashow.cloud.tashowmoduleuserbiz.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.tashow.cloud.model.SystemRole;
import org.apache.ibatis.annotations.Mapper;
/**
* 系统角色Mapper接口
*/
@Mapper
public interface SystemRoleMapper extends BaseMapper<SystemRole> {
}

View File

@@ -0,0 +1,13 @@
package com.tashow.cloud.tashowmoduleuserbiz.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.tashow.cloud.model.SystemRoleMenu;
import org.apache.ibatis.annotations.Mapper;
/**
* 角色菜单关联Mapper接口
*/
@Mapper
public interface SystemRoleMenuMapper extends BaseMapper<SystemRoleMenu> {
}

View File

@@ -0,0 +1,13 @@
package com.tashow.cloud.tashowmoduleuserbiz.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.tashow.cloud.model.SystemUser;
import org.apache.ibatis.annotations.Mapper;
/**
* 系统用户Mapper接口
*/
@Mapper
public interface SystemUserMapper extends BaseMapper<SystemUser> {
}

View File

@@ -0,0 +1,13 @@
package com.tashow.cloud.tashowmoduleuserbiz.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.tashow.cloud.model.SystemUserPost;
import org.apache.ibatis.annotations.Mapper;
/**
* 用户岗位关联Mapper接口
*/
@Mapper
public interface SystemUserPostMapper extends BaseMapper<SystemUserPost> {
}

View File

@@ -0,0 +1,13 @@
package com.tashow.cloud.tashowmoduleuserbiz.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.tashow.cloud.model.SystemUserRole;
import org.apache.ibatis.annotations.Mapper;
/**
* 用户角色关联Mapper接口
*/
@Mapper
public interface SystemUserRoleMapper extends BaseMapper<SystemUserRole> {
}

View File

@@ -0,0 +1,44 @@
package com.tashow.cloud.tashowmoduleuserbiz.security.config;
import cn.dev33.satoken.interceptor.SaInterceptor;
import cn.dev33.satoken.router.SaRouter;
import cn.dev33.satoken.sso.SaSsoProcessor;
import cn.dev33.satoken.stp.StpUtil;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* Sa-Token 配置类
*/
@Configuration
public class SaTokenConfigure implements WebMvcConfigurer {
/**
* 注册Sa-Token拦截器打开注解式鉴权功能
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册路由拦截器,自定义认证规则
registry.addInterceptor(new SaInterceptor(handler -> {
// 登录认证 -- 排除登录接口外,其他接口需要登录才能访问
SaRouter.match("/**")
.notMatch("/user/doLogin")
.notMatch("/sso/*")
.notMatch("/oauth2/*")
.check(r -> StpUtil.checkLogin());
})).addPathPatterns("/**");
}
/**
* SSO-Client端处理所有SSO相关请求
* 配置此函数后,可在客户端端通过以下方式完成单点登录功能:
* http://{host}:{port}/sso/login —— Client端登录地址接受参数back=登录后的跳转地址
* http://{host}:{port}/sso/logout —— Client端单点注销地址isSlo=true时打开接受参数back=注销后的跳转地址
* http://{host}:{port}/sso/logoutCall —— Client端单点注销回调地址isSlo=true时打开此接口为框架回调开发者无需关心
*/
public void ssoClientDister() {
// 处理SSO相关请求
SaSsoProcessor.instance.clientDister();
}
}

View File

@@ -24,7 +24,8 @@ public class SecurityConfiguration {
registry.requestMatchers("/v3/api-docs/**").permitAll()
.requestMatchers("/webjars/**").permitAll()
.requestMatchers("/swagger-ui").permitAll()
.requestMatchers("/swagger-ui/**").permitAll();
.requestMatchers("/swagger-ui/**").permitAll()
;
// Druid 监控
registry.requestMatchers("/druid/**").permitAll();
// Spring Boot Actuator 的安全配置

View File

@@ -0,0 +1,269 @@
package com.tashow.cloud.tashowmoduleuserbiz.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tashow.cloud.model.SystemRole;
import com.tashow.cloud.model.SystemRoleMenu;
import com.tashow.cloud.model.SystemUserRole;
import com.tashow.cloud.service.SystemRoleService;
import com.tashow.cloud.tashowmoduleuserbiz.mapper.SystemRoleMapper;
import com.tashow.cloud.tashowmoduleuserbiz.mapper.SystemRoleMenuMapper;
import com.tashow.cloud.tashowmoduleuserbiz.mapper.SystemUserRoleMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
/**
* 系统角色服务实现类
*/
@Service
public class SystemRoleServiceImpl extends ServiceImpl<SystemRoleMapper, SystemRole> implements SystemRoleService {
@Autowired
private SystemRoleMenuMapper roleMenuMapper;
@Autowired
private SystemUserRoleMapper userRoleMapper;
@Override
@Transactional(rollbackFor = Exception.class)
public boolean createRole(SystemRole role, List<Long> menuIds) {
// 设置默认值
LocalDateTime now = LocalDateTime.now();
role.setCreateTime(now);
role.setUpdateTime(now);
role.setDeleted(false);
role.setTenantId(0L); // 默认租户ID
// 保存角色
boolean saved = save(role);
if (!saved) {
return false;
}
// 保存角色菜单关联
if (!CollectionUtils.isEmpty(menuIds)) {
batchInsertRoleMenus(role.getId(), menuIds, role.getCreator());
}
return true;
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean updateRole(SystemRole role, List<Long> menuIds) {
// 更新角色基本信息
role.setUpdateTime(LocalDateTime.now());
// 更新角色
boolean updated = updateById(role);
if (!updated) {
return false;
}
// 更新角色菜单关联
if (menuIds != null) {
// 先删除旧的关联
deleteRoleMenusByRoleId(role.getId());
// 添加新的关联
if (!menuIds.isEmpty()) {
batchInsertRoleMenus(role.getId(), menuIds, role.getUpdater());
}
}
return true;
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean deleteRole(Long roleId) {
// 逻辑删除角色
return removeById(roleId);
}
@Override
public SystemRole getRoleById(Long roleId) {
return getById(roleId);
}
@Override
public SystemRole getRoleByCode(String code) {
return getOne(new LambdaQueryWrapper<SystemRole>()
.eq(SystemRole::getCode, code)
.eq(SystemRole::getStatus, 0)
.eq(SystemRole::getDeleted, false));
}
@Override
public List<SystemRole> getAllRoles() {
return list(new LambdaQueryWrapper<SystemRole>()
.eq(SystemRole::getDeleted, false)
.orderByAsc(SystemRole::getSort));
}
@Override
public List<SystemRole> getRolesByStatus(Integer status) {
return list(new LambdaQueryWrapper<SystemRole>()
.eq(SystemRole::getStatus, status)
.eq(SystemRole::getDeleted, false)
.orderByAsc(SystemRole::getSort));
}
/**
* 删除角色菜单关联(逻辑删除)
*
* @param roleId 角色ID
* @return 影响行数
*/
public int deleteRoleMenus(Long roleId) {
// 创建更新条件
LambdaUpdateWrapper<SystemRoleMenu> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(SystemRoleMenu::getRoleId, roleId)
.eq(SystemRoleMenu::getDeleted, false)
.set(SystemRoleMenu::getDeleted, true)
.set(SystemRoleMenu::getUpdateTime, LocalDateTime.now());
// 执行更新
return roleMenuMapper.update(null, updateWrapper);
}
/**
* 根据角色ID删除角色菜单关联逻辑删除
*
* @param roleId 角色ID
* @return 影响行数
*/
public int deleteRoleMenusByRoleId(Long roleId) {
// 创建更新条件
LambdaUpdateWrapper<SystemRoleMenu> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(SystemRoleMenu::getRoleId, roleId)
.eq(SystemRoleMenu::getDeleted, false)
.set(SystemRoleMenu::getDeleted, true)
.set(SystemRoleMenu::getUpdateTime, LocalDateTime.now());
// 执行更新
return roleMenuMapper.update(null, updateWrapper);
}
/**
* 批量插入角色菜单关联
*
* @param roleId 角色ID
* @param menuIds 菜单ID列表
* @param creator 创建者
* @return 影响行数
*/
public int batchInsertRoleMenus(Long roleId, List<Long> menuIds, String creator) {
if (CollectionUtils.isEmpty(menuIds)) {
return 0;
}
LocalDateTime now = LocalDateTime.now();
List<SystemRoleMenu> roleMenus = new ArrayList<>(menuIds.size());
for (Long menuId : menuIds) {
SystemRoleMenu roleMenu = new SystemRoleMenu();
roleMenu.setRoleId(roleId);
roleMenu.setMenuId(menuId);
roleMenu.setCreator(creator);
roleMenu.setUpdater(creator);
roleMenu.setCreateTime(now);
roleMenu.setUpdateTime(now);
roleMenu.setDeleted(false);
roleMenu.setTenantId(0L);
roleMenus.add(roleMenu);
}
// 批量插入
int count = 0;
for (SystemRoleMenu roleMenu : roleMenus) {
count += roleMenuMapper.insert(roleMenu);
}
return count;
}
@Override
public List<Long> getRoleMenuIds(Long roleId) {
// 使用LambdaQueryWrapper查询
LambdaQueryWrapper<SystemRoleMenu> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(SystemRoleMenu::getRoleId, roleId)
.eq(SystemRoleMenu::getDeleted, false)
.select(SystemRoleMenu::getMenuId);
// 执行查询并转换结果
return roleMenuMapper.selectList(queryWrapper)
.stream()
.map(SystemRoleMenu::getMenuId)
.collect(Collectors.toList());
}
/**
* 根据角色ID列表获取菜单ID列表
*
* @param roleIds 角色ID列表
* @return 菜单ID列表
*/
public List<Long> getMenuIdsByRoleIds(List<Long> roleIds) {
if (roleIds == null || roleIds.isEmpty()) {
return Collections.emptyList();
}
// 创建查询条件
LambdaQueryWrapper<SystemRoleMenu> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.in(SystemRoleMenu::getRoleId, roleIds)
.eq(SystemRoleMenu::getDeleted, false)
.select(SystemRoleMenu::getMenuId);
// 执行查询并转换结果,确保唯一性
return roleMenuMapper.selectList(queryWrapper)
.stream()
.map(SystemRoleMenu::getMenuId)
.distinct()
.collect(Collectors.toList());
}
@Override
public List<SystemRole> getRolesByUserId(Long userId) {
// 先获取用户的角色ID列表
LambdaQueryWrapper<SystemUserRole> urQueryWrapper = new LambdaQueryWrapper<>();
urQueryWrapper.eq(SystemUserRole::getUserId, userId)
.eq(SystemUserRole::getDeleted, false)
.select(SystemUserRole::getRoleId);
List<Long> roleIds = userRoleMapper.selectList(urQueryWrapper)
.stream()
.map(SystemUserRole::getRoleId)
.collect(Collectors.toList());
if (roleIds.isEmpty()) {
return Collections.emptyList();
}
// 然后根据角色ID查询角色信息
LambdaQueryWrapper<SystemRole> rQueryWrapper = new LambdaQueryWrapper<>();
rQueryWrapper.in(SystemRole::getId, roleIds)
.eq(SystemRole::getStatus, 0)
.eq(SystemRole::getDeleted, false);
return baseMapper.selectList(rQueryWrapper);
}
@Override
public boolean updateRoleStatus(Long roleId, Integer status) {
SystemRole role = new SystemRole();
role.setId(roleId);
role.setStatus(status);
role.setUpdateTime(LocalDateTime.now());
return updateById(role);
}
}

View File

@@ -0,0 +1,425 @@
package com.tashow.cloud.tashowmoduleuserbiz.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tashow.cloud.model.SystemRole;
import com.tashow.cloud.model.SystemUser;
import com.tashow.cloud.model.SystemUserRole;
import com.tashow.cloud.model.SystemUserPost;
import com.tashow.cloud.service.SystemUserService;
import com.tashow.cloud.tashowmoduleuserbiz.mapper.SystemUserMapper;
import com.tashow.cloud.tashowmoduleuserbiz.mapper.SystemUserRoleMapper;
import com.tashow.cloud.tashowmoduleuserbiz.mapper.SystemUserPostMapper;
import com.tashow.cloud.tashowmoduleuserbiz.mapper.SystemRoleMapper;
import com.tashow.cloud.convert.SystemRoleConvert;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
/**
* 系统用户服务实现类
* 基于MyBatis Plus的查询方式实现
*/
@Service
public class SystemUserServiceImpl extends ServiceImpl<SystemUserMapper, SystemUser> implements SystemUserService {
private final BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
@Autowired
private SystemUserRoleMapper userRoleMapper;
@Autowired
private SystemUserPostMapper userPostMapper;
@Autowired
private SystemRoleMapper roleMapper;
@Override
public SystemUser getUserByUsername(String username) {
return getOne(new LambdaQueryWrapper<SystemUser>()
.eq(SystemUser::getUsername, username)
.eq(SystemUser::getStatus, 0)
.eq(SystemUser::getDeleted, false));
}
@Override
public boolean validatePassword(String rawPassword, String encodedPassword) {
return passwordEncoder.matches(rawPassword, encodedPassword);
}
@Override
public SystemUser getUserById(Long userId) {
return getById(userId);
}
@Override
public void updateLoginInfo(Long userId, String ip) {
SystemUser user = new SystemUser();
user.setId(userId);
user.setLoginIp(ip);
LocalDateTime now = LocalDateTime.now();
user.setLoginDate(now);
user.setUpdateTime(now);
updateById(user);
}
@Override
public boolean registerUser(SystemUser user) {
// 设置默认值
LocalDateTime now = LocalDateTime.now();
user.setCreateTime(now);
user.setUpdateTime(now);
user.setLoginDate(now);
user.setCreator("system");
user.setUpdater("system");
if (user.getAvatar() == null || user.getAvatar().isEmpty()) {
user.setAvatar("https://sa-token.cc/logo.png"); // 设置默认头像
}
user.setTenantId(0L); // 默认租户ID
user.setDeleted(false); // 设置为未删除
return save(user);
}
@Override
public String encodePassword(String rawPassword) {
return passwordEncoder.encode(rawPassword);
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean createUser(SystemUser user, List<Long> roleIds, List<Long> postIds) {
// 设置默认值
LocalDateTime now = LocalDateTime.now();
user.setCreateTime(now);
user.setUpdateTime(now);
user.setLoginDate(now);
if (user.getAvatar() == null || user.getAvatar().isEmpty()) {
user.setAvatar("https://sa-token.cc/logo.png"); // 设置默认头像
}
user.setTenantId(0L); // 默认租户ID
user.setDeleted(false); // 设置为未删除
// 加密密码
user.setPassword(encodePassword(user.getPassword()));
// 保存用户
boolean saved = save(user);
if (!saved) {
return false;
}
// 保存用户角色关联
if (!CollectionUtils.isEmpty(roleIds)) {
batchInsertUserRoles(user.getId(), roleIds, user.getCreator());
}
// 保存用户岗位关联
if (!CollectionUtils.isEmpty(postIds)) {
batchInsertUserPosts(user.getId(), postIds, user.getCreator());
}
return true;
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean updateUser(SystemUser user, List<Long> roleIds, List<Long> postIds) {
// 更新用户基本信息
user.setUpdateTime(LocalDateTime.now());
// 防止修改密码
user.setPassword(null);
// 更新用户
boolean updated = updateById(user);
if (!updated) {
return false;
}
// 更新用户角色关联
if (roleIds != null) {
// 先删除旧的关联
deleteUserRolesByUserId(user.getId());
// 添加新的关联
if (!roleIds.isEmpty()) {
batchInsertUserRoles(user.getId(), roleIds, user.getUpdater());
}
}
// 更新用户岗位关联
if (postIds != null) {
// 先删除旧的关联
deleteUserPostsByUserId(user.getId());
// 添加新的关联
if (!postIds.isEmpty()) {
batchInsertUserPosts(user.getId(), postIds, user.getUpdater());
}
}
return true;
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean deleteUser(Long userId) {
// 逻辑删除用户
return removeById(userId);
}
@Override
public boolean resetUserPassword(Long userId, String newPassword) {
SystemUser user = new SystemUser();
user.setId(userId);
user.setPassword(encodePassword(newPassword));
user.setUpdateTime(LocalDateTime.now());
return updateById(user);
}
@Override
public boolean updateUserStatus(Long userId, Integer status) {
SystemUser user = new SystemUser();
user.setId(userId);
user.setStatus(status);
user.setUpdateTime(LocalDateTime.now());
return updateById(user);
}
/**
* 删除用户角色关联(逻辑删除)
*
* @param userId 用户ID
* @return 影响行数
*/
public int deleteUserRoles(Long userId) {
// 创建更新条件
LambdaUpdateWrapper<SystemUserRole> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(SystemUserRole::getUserId, userId)
.eq(SystemUserRole::getDeleted, false)
.set(SystemUserRole::getDeleted, true)
.set(SystemUserRole::getUpdateTime, LocalDateTime.now());
// 执行更新
return userRoleMapper.update(null, updateWrapper);
}
/**
*
* 根据用户ID删除用户角色关联逻辑删除
*
* @param userId 用户ID
* @return 影响行数
*/
public int deleteUserRolesByUserId(Long userId) {
// 创建更新条件
LambdaUpdateWrapper<SystemUserRole> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(SystemUserRole::getUserId, userId)
.eq(SystemUserRole::getDeleted, false)
.set(SystemUserRole::getDeleted, true)
.set(SystemUserRole::getUpdateTime, LocalDateTime.now());
// 执行更新
return userRoleMapper.update(null, updateWrapper);
}
/**
* 根据用户ID删除用户岗位关联逻辑删除
*
* @param userId 用户ID
* @return 影响行数
*/
public int deleteUserPostsByUserId(Long userId) {
// 创建更新条件
LambdaUpdateWrapper<SystemUserPost> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(SystemUserPost::getUserId, userId)
.eq(SystemUserPost::getDeleted, false)
.set(SystemUserPost::getDeleted, true)
.set(SystemUserPost::getUpdateTime, LocalDateTime.now());
// 执行更新
return userPostMapper.update(null, updateWrapper);
}
/**
* 批量插入用户角色关联
*
* @param userId 用户ID
* @param roleIds 角色ID列表
* @param creator 创建者
* @return 影响行数
*/
public int batchInsertUserRoles(Long userId, List<Long> roleIds, String creator) {
if (CollectionUtils.isEmpty(roleIds)) {
return 0;
}
LocalDateTime now = LocalDateTime.now();
List<SystemUserRole> userRoles = new ArrayList<>(roleIds.size());
for (Long roleId : roleIds) {
SystemUserRole userRole = new SystemUserRole();
userRole.setUserId(userId);
userRole.setRoleId(roleId);
userRole.setCreator(creator);
userRole.setUpdater(creator);
userRole.setCreateTime(now);
userRole.setUpdateTime(now);
userRole.setDeleted(false);
userRole.setTenantId(0L);
userRoles.add(userRole);
}
// 批量插入
int count = 0;
for (SystemUserRole userRole : userRoles) {
count += userRoleMapper.insert(userRole);
}
return count;
}
/**
* 批量插入用户岗位关联
*
* @param userId 用户ID
* @param postIds 岗位ID列表
* @param creator 创建者
* @return 影响行数
*/
public int batchInsertUserPosts(Long userId, List<Long> postIds, String creator) {
if (CollectionUtils.isEmpty(postIds)) {
return 0;
}
LocalDateTime now = LocalDateTime.now();
List<SystemUserPost> userPosts = new ArrayList<>(postIds.size());
for (Long postId : postIds) {
SystemUserPost userPost = new SystemUserPost();
userPost.setUserId(userId);
userPost.setPostId(postId);
userPost.setCreator(creator);
userPost.setUpdater(creator);
userPost.setCreateTime(now);
userPost.setUpdateTime(now);
userPost.setDeleted(false);
userPost.setTenantId(0L);
userPosts.add(userPost);
}
// 批量插入
int count = 0;
for (SystemUserPost userPost : userPosts) {
count += userPostMapper.insert(userPost);
}
return count;
}
/**
* 删除用户岗位关联(逻辑删除)
*
* @param userId 用户ID
* @return 影响行数
*/
public int deleteUserPosts(Long userId) {
// 创建更新条件
LambdaUpdateWrapper<SystemUserPost> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(SystemUserPost::getUserId, userId)
.eq(SystemUserPost::getDeleted, false)
.set(SystemUserPost::getDeleted, true)
.set(SystemUserPost::getUpdateTime, LocalDateTime.now());
// 执行更新
return userPostMapper.update(null, updateWrapper);
}
@Override
public List<Long> getUserRoleIds(Long userId) {
// 使用LambdaQueryWrapper查询
LambdaQueryWrapper<SystemUserRole> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(SystemUserRole::getUserId, userId)
.eq(SystemUserRole::getDeleted, false)
.select(SystemUserRole::getRoleId);
return userRoleMapper.selectList(queryWrapper)
.stream()
.map(SystemUserRole::getRoleId)
.collect(Collectors.toList());
}
@Override
public List<String> getUserRoleNames(Long userId) {
// 先获取用户角色
List<SystemRole> roles = getUserRoles(userId);
// 提取角色名称
return roles.stream()
.map(SystemRole::getName)
.collect(Collectors.toList());
}
@Override
public List<String> getUserRoleCodes(Long userId) {
// 先获取用户角色
List<SystemRole> roles = getUserRoles(userId);
// 提取角色编码
return roles.stream()
.map(SystemRole::getCode)
.collect(Collectors.toList());
}
/**
* 获取用户角色列表
*
* @param userId 用户ID
* @return 角色列表
*/
private List<SystemRole> getUserRoles(Long userId) {
// 先获取用户的角色ID列表
List<Long> roleIds = getUserRoleIds(userId);
if (roleIds.isEmpty()) {
return Collections.emptyList();
}
// 查询这些角色的详细信息
LambdaQueryWrapper<SystemRole> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.in(SystemRole::getId, roleIds)
.eq(SystemRole::getStatus, 0)
.eq(SystemRole::getDeleted, false);
return roleMapper.selectList(queryWrapper);
}
@Override
public List<Long> getUserPostIds(Long userId) {
// 使用LambdaQueryWrapper查询
LambdaQueryWrapper<SystemUserPost> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(SystemUserPost::getUserId, userId)
.eq(SystemUserPost::getDeleted, false)
.select(SystemUserPost::getPostId);
// 执行查询并转换结果
return userPostMapper.selectList(queryWrapper)
.stream()
.map(SystemUserPost::getPostId)
.collect(Collectors.toList());
}
}

View File

@@ -0,0 +1,63 @@
package com.tashow.cloud.user.convert;
import com.tashow.cloud.user.model.Role;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
/**
* 角色对象转换器
*/
@Mapper
public interface RoleConvert {
RoleConvert INSTANCE = Mappers.getMapper(RoleConvert.class);
/**
* 将角色列表转换为角色ID列表
*
* @param roles 角色列表
* @return 角色ID列表
*/
default List<Long> convertToRoleIds(List<Role> roles) {
if (roles == null || roles.isEmpty()) {
return Collections.emptyList();
}
return roles.stream()
.map(Role::getId)
.collect(Collectors.toList());
}
/**
* 将角色列表转换为角色名称列表
*
* @param roles 角色列表
* @return 角色名称列表
*/
default List<String> convertToRoleNames(List<Role> roles) {
if (roles == null || roles.isEmpty()) {
return Collections.emptyList();
}
return roles.stream()
.map(Role::getName)
.collect(Collectors.toList());
}
/**
* 将角色列表转换为角色编码列表
*
* @param roles 角色列表
* @return 角色编码列表
*/
default List<String> convertToRoleCodes(List<Role> roles) {
if (roles == null || roles.isEmpty()) {
return Collections.emptyList();
}
return roles.stream()
.map(Role::getCode)
.collect(Collectors.toList());
}
}

View File

@@ -0,0 +1,136 @@
package com.tashow.cloud.user.convert;
import com.tashow.cloud.user.dto.UserCreateReqDTO;
import com.tashow.cloud.user.dto.UserRegisterReqDTO;
import com.tashow.cloud.user.dto.UserRespDTO;
import com.tashow.cloud.user.dto.UserUpdateReqDTO;
import com.tashow.cloud.user.model.User;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;
import java.util.List;
import java.util.stream.Collectors;
/**
* 用户对象转换器
*/
@Mapper
public interface UserConvert {
UserConvert INSTANCE = Mappers.getMapper(UserConvert.class);
/**
* 用户创建请求DTO转实体
*/
@Mapping(target = "roles", ignore = true)
@Mapping(target = "createTime", ignore = true)
@Mapping(target = "updateTime", ignore = true)
@Mapping(target = "loginDate", ignore = true)
@Mapping(target = "deleted", ignore = true)
@Mapping(target = "loginIp", ignore = true)
@Mapping(target = "postIds", expression = "java(postIdsToString(bean.getPostIds()))")
User convert(UserCreateReqDTO bean);
/**
* 用户更新请求DTO转实体
*/
@Mapping(target = "roles", ignore = true)
@Mapping(target = "username", ignore = true)
@Mapping(target = "password", ignore = true)
@Mapping(target = "createTime", ignore = true)
@Mapping(target = "updateTime", ignore = true)
@Mapping(target = "loginDate", ignore = true)
@Mapping(target = "deleted", ignore = true)
@Mapping(target = "loginIp", ignore = true)
@Mapping(target = "postIds", expression = "java(postIdsToString(bean.getPostIds()))")
User convert(UserUpdateReqDTO bean);
/**
* 用户注册请求DTO转实体
*/
@Mapping(target = "roles", ignore = true)
@Mapping(target = "id", ignore = true)
@Mapping(target = "createTime", ignore = true)
@Mapping(target = "updateTime", ignore = true)
@Mapping(target = "loginDate", ignore = true)
@Mapping(target = "deleted", ignore = true)
@Mapping(target = "deptId", ignore = true)
@Mapping(target = "postIds", ignore = true)
@Mapping(target = "sex", ignore = true)
@Mapping(target = "creator", ignore = true)
@Mapping(target = "updater", ignore = true)
@Mapping(target = "tenantId", ignore = true)
@Mapping(target = "remark", ignore = true)
@Mapping(target = "status", constant = "0")
User convert(UserRegisterReqDTO bean);
/**
* 用户实体转响应DTO
*/
@Mapping(target = "sexName", expression = "java(getSexName(bean.getSex()))")
@Mapping(target = "statusName", expression = "java(getStatusName(bean.getStatus()))")
@Mapping(target = "roleIds", ignore = true)
@Mapping(target = "roleNames", ignore = true)
@Mapping(target = "roleCodes", ignore = true)
@Mapping(target = "postIds", expression = "java(stringToPostIds(bean.getPostIds()))")
@Mapping(target = "postNames", ignore = true)
@Mapping(target = "deptName", ignore = true)
UserRespDTO convert(User bean);
/**
* 用户实体列表转响应DTO列表
*/
List<UserRespDTO> convertList(List<User> list);
/**
* 岗位ID列表转字符串
*/
default String postIdsToString(List<Long> postIds) {
if (postIds == null || postIds.isEmpty()) {
return null;
}
return postIds.stream()
.map(String::valueOf)
.collect(Collectors.joining(","));
}
/**
* 字符串转岗位ID列表
*/
default List<Long> stringToPostIds(String postIds) {
if (postIds == null || postIds.isEmpty()) {
return java.util.Collections.emptyList();
}
return java.util.Arrays.stream(postIds.split(","))
.map(Long::valueOf)
.collect(Collectors.toList());
}
/**
* 获取性别名称
*/
default String getSexName(Integer sex) {
if (sex == null) {
return null;
}
return switch (sex) {
case 1 -> "";
case 2 -> "";
default -> "未知";
};
}
/**
* 获取状态名称
*/
default String getStatusName(Integer status) {
if (status == null) {
return null;
}
return switch (status) {
case 0 -> "正常";
case 1 -> "停用";
default -> "未知";
};
}
}

View File

@@ -0,0 +1,13 @@
/**
* 用户中心模块业务层实现
*
* <p>此模块包含以下主要组件:
* <ul>
* <li>controller - 控制器层负责接收和响应HTTP请求</li>
* <li>service - 服务实现层,实现业务逻辑</li>
* <li>mapper - 数据访问层,与数据库交互</li>
* <li>convert - 对象转换层负责DTO和实体间的转换</li>
* <li>config - 配置类</li>
* </ul>
*/
package com.tashow.cloud.user;

View File

@@ -0,0 +1 @@
com.tashow.cloud.tashowmoduleuserbiz.framework.core.RedisCaptchaServiceImpl

View File

@@ -0,0 +1,66 @@
--- #################### 注册中心 + 配置中心相关配置 ####################
spring:
cloud:
nacos:
server-addr: 43.139.42.137:8848 # Nacos 服务器地址
username: nacos # Nacos 账号
password: nacos # Nacos 密码
discovery: # 【配置中心】配置项
namespace: 5c8b8fe6-9a89-4ae3-975e-ef3bf560ff82 # 命名空间。这里使用 dev 开发环境
group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP
metadata:
version: 1.0.0 # 服务实例的版本号,可用于灰度发布
config: # 【注册中心】配置项
namespace: 5c8b8fe6-9a89-4ae3-975e-ef3bf560ff82 # 命名空间。这里使用 dev 开发环境
group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP
# 数据源配置项
autoconfigure:
exclude:
- de.codecentric.boot.admin.client.config.SpringBootAdminClientAutoConfiguration # 禁用 Spring Boot Admin 的 Client 的自动配置
datasource:
druid: # Druid 【监控】相关的全局配置
web-stat-filter:
enabled: true
stat-view-servlet:
enabled: true
allow: # 设置白名单,不填则允许所有访问
url-pattern: /druid/*
login-username: # 控制台管理用户名和密码
login-password:
filter:
stat:
enabled: true
log-slow-sql: true # 慢 SQL 记录
slow-sql-millis: 100
merge-sql: true
wall:
config:
multi-statement-allow: true
dynamic: # 多数据源配置
druid: # Druid 【连接池】相关的全局配置
initial-size: 1 # 初始连接数
min-idle: 1 # 最小连接池数量
max-active: 20 # 最大连接池数量
max-wait: 600000 # 配置获取连接等待超时的时间,单位:毫秒
time-between-eviction-runs-millis: 60000 # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位:毫秒
min-evictable-idle-time-millis: 300000 # 配置一个连接在池中最小生存的时间,单位:毫秒
max-evictable-idle-time-millis: 900000 # 配置一个连接在池中最大生存的时间,单位:毫秒
validation-query: SELECT 1 FROM DUAL # 配置检测连接是否有效
test-while-idle: true
test-on-borrow: false
test-on-return: false
primary: master
datasource:
master:
url: jdbc:mysql://43.139.42.137:8406/tashow-platform?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true # MySQL Connector/J 8.X 连接的示例
username: tashow-platform
password: tashow123,
# slave: # 模拟从库,可根据自己需要修改
# lazy: true # 开启懒加载,保证启动速度
# url: jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true
# username: root
# password: 123456

View File

@@ -0,0 +1,229 @@
server:
port: 48082
spring:
application:
name: user-server
profiles:
active: local
main:
allow-circular-references: true # 允许循环依赖,因为项目是三层架构,无法避免这个情况。
allow-bean-definition-overriding: true # 允许 Bean 覆盖,例如说 Feign 等会存在重复定义的服务
config:
import:
- optional:classpath:application-${spring.profiles.active}.yaml # 加载【本地】配置
- optional:nacos:${spring.application.name}-${spring.profiles.active}.yaml # 加载【Nacos】的配置
# Servlet 配置
servlet:
# 文件上传相关配置项
multipart:
max-file-size: 16MB # 单个文件大小
max-request-size: 32MB # 设置总上传的文件大小
# Jackson 配置项
jackson:
serialization:
write-dates-as-timestamps: true # 设置 LocalDateTime 的格式,使用时间戳
write-date-timestamps-as-nanoseconds: false # 设置不使用 nanoseconds 的格式。例如说 1611460870.401,而是直接 1611460870401
write-durations-as-timestamps: true # 设置 Duration 的格式,使用时间戳
fail-on-empty-beans: false # 允许序列化无属性的 Bean
# Cache 配置项
cache:
type: REDIS
redis:
time-to-live: 1h # 设置过期时间为 1 小时
logging:
file:
name: ${user.home}/logs/${spring.application.name}.log # 日志文件名,全路径
--- #################### 接口文档配置 ####################
springdoc:
api-docs:
enabled: true # 1. 是否开启 Swagger 接文档的元数据
path: /v3/api-docs
swagger-ui:
enabled: true # 2.1 是否开启 Swagger 文档的官方 UI 界面
path: /swagger-ui
default-flat-param-object: true # 参见 https://doc.xiaominfo.com/docs/faq/v4/knife4j-parameterobject-flat-param 文档
knife4j:
enable: true # 2.2 是否开启 Swagger 文档的 Knife4j UI 界面
setting:
language: zh_cn
# MyBatis Plus 的配置项
mybatis-plus:
configuration:
map-underscore-to-camel-case: true #虽然默认为 true ,但是还是显示去指定下。
global-config:
db-config:
id-type: NONE # "智能"模式,基于 IdTypeEnvironmentPostProcessor + 数据源的类型,自动适配成 AUTO、INPUT 模式。
logic-delete-value: 1 # 逻辑已删除值(默认为 1)
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
banner: false # 关闭控制台的 Banner 打印
type-aliases-package: ${tashow.info.base-package}.dal.dataobject
encryptor:
password: XDV71a+xqStEA3WH # 加解密的秘钥,可使用 https://www.imaegoo.com/2020/aes-key-generator/ 网站生成
mybatis-plus-join:
banner: false # 关闭控制台的 Banner 打印
# Spring Data Redis 配置
spring:
data:
redis:
repositories:
enabled: false # 项目未使用到 Spring Data Redis 的 Repository所以直接禁用保证启动速度
# VO 转换(数据翻译)相关
easy-trans:
is-enable-global: true # 启用全局翻译(拦截所有 SpringMVC ResponseBody 进行自动翻译 )。如果对于性能要求很高可关闭此配置,或通过 @IgnoreTrans 忽略某个接口
--- #################### RPC 远程调用相关配置 ####################
--- #################### 消息队列相关 ####################
spring:
# Kafka 配置项,对应 KafkaProperties 配置类
kafka:
# Kafka Producer 配置项
producer:
acks: 1 # 0-不应答。1-leader 应答。all-所有 leader 和 follower 应答。
retries: 3 # 发送失败时,重试发送的次数
value-serializer: org.springframework.kafka.support.serializer.JsonSerializer # 消息的 value 的序列化
# Kafka Consumer 配置项
consumer:
auto-offset-reset: earliest # 设置消费者分组最初的消费进度为 earliest 。可参考博客 https://blog.csdn.net/lishuangzhe7047/article/details/74530417 理解
value-deserializer: org.springframework.kafka.support.serializer.JsonDeserializer
properties:
spring.json.trusted.packages: '*'
# Kafka Consumer Listener 监听器配置
listener:
missing-topics-fatal: false # 消费监听接口监听的主题不存在时,默认会报错。所以通过设置为 false ,解决报错
--- #################### 定时任务相关配置 ####################
xxl:
job:
executor:
appname: ${spring.application.name} # 执行器 AppName
logpath: ${user.home}/logs/xxl-job/${spring.application.name} # 执行器运行日志文件存储磁盘路径
accessToken: default_token # 执行器通讯TOKEN
--- #################### 验证码相关配置 ####################
aj:
captcha:
jigsaw: classpath:images/jigsaw # 滑动验证,底图路径,不配置将使用默认图片;以 classpath: 开头,取 resource 目录下路径
pic-click: classpath:images/pic-click # 滑动验证,底图路径,不配置将使用默认图片;以 classpath: 开头,取 resource 目录下路径
cache-type: redis # 缓存 local/redis...
cache-number: 1000 # local 缓存的阈值,达到这个值,清除缓存
timing-clear: 180 # local定时清除过期缓存(单位秒),设置为0代表不执行
type: blockPuzzle # 验证码类型 default两种都实例化。 blockPuzzle 滑块拼图 clickWord 文字点选
water-mark: 他秀 # 右下角水印文字(我的水印),可使用 https://tool.chinaz.com/tools/unicode.aspx 中文转 UnicodeLinux 可能需要转 unicode
interference-options: 0 # 滑动干扰项(0/1/2)
req-frequency-limit-enable: false # 接口请求次数一分钟限制是否开启 true|false
req-get-lock-limit: 5 # 验证失败5次get接口锁定
req-get-lock-seconds: 10 # 验证失败后,锁定时间间隔
req-get-minute-limit: 30 # get 接口一分钟内请求数限制
req-check-minute-limit: 60 # check 接口一分钟内请求数限制
req-verify-minute-limit: 60 # verify 接口一分钟内请求数限制
--- #################### 芋道相关配置 ####################
tashow:
info:
version: 1.0.0
base-package: com.tashow.cloud.system
web:
admin-ui:
url: http://dashboard.yudao.iocoder.cn # Admin 管理后台 UI 的地址
xss:
enable: false
exclude-urls: # 如下 url仅仅是为了演示去掉配置也没关系
- ${management.endpoints.web.base-path}/** # 不处理 Actuator 的请求
swagger:
title: 管理后台
description: 提供管理员管理的所有功能
version: ${tashow.info.version}
tenant: # 多租户相关配置项
enable: true
ignore-urls:
- /admin-api/system/tenant/get-id-by-name # 基于名字获取租户,不许带租户编号
- /admin-api/system/tenant/get-by-website # 基于域名获取租户,不许带租户编号
- /admin-api/system/captcha/get-image # 获取图片验证码,和租户无关
- /admin-api/system/captcha/get # 获取图片验证码,和租户无关
- /admin-api/system/captcha/check # 校验图片验证码,和租户无关
- /admin-api/system/sms/callback/* # 短信回调接口,无法带上租户编号
- /rpc-api/system/tenant/valid # 防止递归。避免调用 /rpc-api/system/tenant/valid 接口时,又去触发 /rpc-api/system/tenant/valid 去校验
- /rpc-api/system/tenant/id-list # 获得租户列表的时候,无需传递租户编号
- /rpc-api/system/oauth2/token/check # 访问令牌校验时,无需传递租户编号;主要解决上传文件的场景,前端不会传递 tenant-id
ignore-tables:
- system_tenant
- system_tenant_package
- system_dict_data
- system_dict_type
- system_error_code
- system_menu
- system_sms_channel
- system_sms_template
- system_sms_log
- system_sensitive_word
- system_oauth2_client
- system_mail_account
- system_mail_template
- system_mail_log
- system_notify_template
ignore-caches:
- user_role_ids
- permission_menu_ids
- oauth_client
- notify_template
- mail_account
- mail_template
- sms_template
sms-code: # 短信验证码相关的配置项
expire-times: 10m
send-frequency: 1m
send-maximum-quantity-per-day: 10
begin-code: 9999 # 这里配置 9999 的原因是,测试方便。
end-code: 9999 # 这里配置 9999 的原因是,测试方便。
debug: false
sa-token:
# token名称
token-name: satoken
# token有效期
timeout: 2592000
# token临时有效期
activity-timeout: -1
# 是否允许同一账号并发登录
is-concurrent: true
# 多人登录时是否共用一个token
is-share: false
# token风格
token-style: uuid
# 是否输出操作日志
is-log: false
# SSO-Client端相关配置
sso:
# SSO-Server端 统一认证地址
auth-url: http://localhost:48082/sso/auth
# 是否打开单点注销功能
is-slo: true
# 单点登录查询ticket参数名称
ticket-param-name: ticket
# 打开模式三此值为true时需要配置下面所有参数
is-http: true
# 请求秘钥 与SSO-Server端保持一致
secretkey: kQwIOrYvnXmSDkwEiFngrKidMcdrgKor
# SSO-Server端 ticket校验地址
check-ticket-url: http://localhost:48082/sso/checkTicket
# SSO-Server端 查询userinfo地址
userinfo-url: http://localhost:48082/sso/userinfo

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

View File

@@ -0,0 +1,10 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
</body>
</html>

View File

@@ -0,0 +1,76 @@
<configuration>
<!-- 引用 Spring Boot 的 logback 基础配置 -->
<include resource="org/springframework/boot/logging/logback/defaults.xml" />
<!-- 变量 yudao.info.base-package基础业务包 -->
<springProperty scope="context" name="tashow.info.base-package" source="tashow.info.base-package"/>
<!-- 格式化输出:%d 表示日期,%X{tid} SkWalking 链路追踪编号,%thread 表示线程名,%-5level级别从左显示 5 个字符宽度,%msg日志消息%n是换行符 -->
<property name="PATTERN_DEFAULT" value="%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd HH:mm:ss.SSS}} | %highlight(${LOG_LEVEL_PATTERN:-%5p} ${PID:- }) | %boldYellow(%thread [%tid]) %boldGreen(%-40.40logger{39}) | %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}"/>
<!-- 控制台 Appender -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">     
<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
<layout class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.TraceIdPatternLogbackLayout">
<pattern>${PATTERN_DEFAULT}</pattern>
</layout>
</encoder>
</appender>
<!-- 文件 Appender -->
<!-- 参考 Spring Boot 的 file-appender.xml 编写 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
<layout class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.TraceIdPatternLogbackLayout">
<pattern>${PATTERN_DEFAULT}</pattern>
</layout>
</encoder>
<!-- 日志文件名 -->
<file>${LOG_FILE}</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!-- 滚动后的日志文件名 -->
<fileNamePattern>${LOGBACK_ROLLINGPOLICY_FILE_NAME_PATTERN:-${LOG_FILE}.%d{yyyy-MM-dd}.%i.gz}</fileNamePattern>
<!-- 启动服务时,是否清理历史日志,一般不建议清理 -->
<cleanHistoryOnStart>${LOGBACK_ROLLINGPOLICY_CLEAN_HISTORY_ON_START:-false}</cleanHistoryOnStart>
<!-- 日志文件,到达多少容量,进行滚动 -->
<maxFileSize>${LOGBACK_ROLLINGPOLICY_MAX_FILE_SIZE:-10MB}</maxFileSize>
<!-- 日志文件的总大小0 表示不限制 -->
<totalSizeCap>${LOGBACK_ROLLINGPOLICY_TOTAL_SIZE_CAP:-0}</totalSizeCap>
<!-- 日志文件的保留天数 -->
<maxHistory>${LOGBACK_ROLLINGPOLICY_MAX_HISTORY:-30}</maxHistory>
</rollingPolicy>
</appender>
<!-- 异步写入日志,提升性能 -->
<appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
<!-- 不丢失日志。默认的,如果队列的 80% 已满,则会丢弃 TRACT、DEBUG、INFO 级别的日志 -->
<discardingThreshold>0</discardingThreshold>
<!-- 更改默认的队列的深度,该值会影响性能。默认值为 256 -->
<queueSize>256</queueSize>
<appender-ref ref="FILE"/>
</appender>
<!-- SkyWalking GRPC 日志收集实现日志中心。注意SkyWalking 8.4.0 版本开始支持 -->
<appender name="GRPC" class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.log.GRPCLogClientAppender">
<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
<layout class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.TraceIdPatternLogbackLayout">
<pattern>${PATTERN_DEFAULT}</pattern>
</layout>
</encoder>
</appender>
<!-- 本地环境 -->
<springProfile name="local">
<root level="INFO">
<appender-ref ref="STDOUT"/>
<appender-ref ref="GRPC"/> <!-- 本地环境下,如果不想接入 SkyWalking 日志服务,可以注释掉本行 -->
<appender-ref ref="ASYNC"/> <!-- 本地环境下,如果不想打印日志,可以注释掉本行 -->
</root>
</springProfile>
<!-- 其它环境 -->
<springProfile name="dev,test,stage,prod,default">
<root level="INFO">
<appender-ref ref="STDOUT"/>
<appender-ref ref="ASYNC"/>
<appender-ref ref="GRPC"/>
</root>
</springProfile>
</configuration>

View File

@@ -0,0 +1,33 @@
<?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.tashow.cloud.tashowmoduleuserbiz.mapper.SystemRoleMenuMapper">
<!-- 根据角色ID列表获取菜单ID列表 -->
<select id="selectMenuIdsByRoleIds" resultType="java.lang.Long">
SELECT DISTINCT menu_id
FROM system_role_menu
WHERE role_id IN
<foreach collection="roleIds" item="roleId" open="(" separator="," close=")">
#{roleId}
</foreach>
AND deleted = 0
</select>
<!-- 根据角色ID删除角色菜单关联 -->
<update id="deleteByRoleId">
UPDATE system_role_menu
SET deleted = 1,
update_time = NOW()
WHERE role_id = #{roleId}
AND deleted = 0
</update>
<insert id="batchInsert">
INSERT INTO system_role_menu (role_id, menu_id, creator, create_time, updater, update_time, deleted, tenant_id)
VALUES
<foreach collection="menuIds" item="menuId" separator=",">
(#{roleId}, #{menuId}, #{creator}, NOW(), #{creator}, NOW(), 0, 0)
</foreach>
</insert>
</mapper>

View File

@@ -0,0 +1,23 @@
<?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.tashow.cloud.tashowmoduleuserbiz.mapper.SystemUserPostMapper">
<!-- 根据用户ID删除用户岗位关联 -->
<update id="deleteByUserId">
UPDATE system_user_post
SET deleted = 1,
update_time = NOW()
WHERE user_id = #{userId}
AND deleted = 0
</update>
<!-- 批量插入用户岗位关联 -->
<insert id="batchInsert">
INSERT INTO system_user_post (user_id, post_id, creator, create_time, updater, update_time, deleted, tenant_id)
VALUES
<foreach collection="postIds" item="postId" separator=",">
(#{userId}, #{postId}, #{creator}, NOW(), #{creator}, NOW(), 0, 0)
</foreach>
</insert>
</mapper>

View File

@@ -0,0 +1,23 @@
<?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.tashow.cloud.tashowmoduleuserbiz.mapper.SystemUserRoleMapper">
<!-- 根据用户ID删除用户角色关联 -->
<update id="deleteByUserId">
UPDATE system_user_role
SET deleted = 1,
update_time = NOW()
WHERE user_id = #{userId}
AND deleted = 0
</update>
<!-- 批量插入用户角色关联 -->
<insert id="batchInsert">
INSERT INTO system_user_role (user_id, role_id, creator, create_time, updater, update_time, deleted, tenant_id)
VALUES
<foreach collection="roleIds" item="roleId" separator=",">
(#{userId}, #{roleId}, #{creator}, NOW(), #{creator}, NOW(), 0, 0)
</foreach>
</insert>
</mapper>