diff --git a/backend/pom.xml b/backend/pom.xml index 556a08d..db9dfa8 100644 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -142,6 +142,13 @@ hutool-all 5.8.8 + + + + com.alibaba + fastjson + 1.2.83 + diff --git a/backend/src/main/java/com/stdproject/common/OperationLogAspect.java b/backend/src/main/java/com/stdproject/common/OperationLogAspect.java index 82547b3..52af924 100644 --- a/backend/src/main/java/com/stdproject/common/OperationLogAspect.java +++ b/backend/src/main/java/com/stdproject/common/OperationLogAspect.java @@ -7,6 +7,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.stdproject.entity.AppOptLog; import com.stdproject.service.IAppOptLogService; import com.stdproject.utils.JwtUtils; +import com.stdproject.utils.commonUtils; import jakarta.servlet.http.HttpServletRequest; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.JoinPoint; @@ -92,8 +93,8 @@ public class OperationLogAspect { optLog.setModule(operationLog.module()); optLog.setDescription(operationLog.description()); optLog.setMethod(request.getMethod() + " " + request.getRequestURI()); - optLog.setRequestip(getIpAddress(request)); - optLog.setBrowser(getBrowser(request)); + optLog.setRequestip(commonUtils.getIpAddress(request)); + optLog.setBrowser(commonUtils.getBrowser(request)); optLog.setLogtime(LocalDateTime.now()); // 设置操作用户 @@ -150,32 +151,5 @@ public class OperationLogAspect { } } - public static String getBrowser(HttpServletRequest request) { - UserAgent userAgent = UserAgentUtil.parse(request.getHeader("User-Agent")); - Browser browser = userAgent.getBrowser(); - return browser.getName(); - } - /** - * 获取客户端IP地址 - */ - private String getIpAddress(HttpServletRequest request) { - String ip = request.getHeader("X-Forwarded-For"); - 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.getHeader("HTTP_CLIENT_IP"); - } - if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { - ip = request.getHeader("HTTP_X_FORWARDED_FOR"); - } - if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { - ip = request.getRemoteAddr(); - } - return ip; - } } diff --git a/backend/src/main/java/com/stdproject/config/CustomUserDetailsService.java b/backend/src/main/java/com/stdproject/config/CustomUserDetailsService.java deleted file mode 100644 index 71255ba..0000000 --- a/backend/src/main/java/com/stdproject/config/CustomUserDetailsService.java +++ /dev/null @@ -1,197 +0,0 @@ -package com.stdproject.config; - -import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; -import com.stdproject.entity.User; -import com.stdproject.service.IMenuService; -import com.stdproject.service.IRoleService; -import com.stdproject.service.IUserService; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.core.userdetails.UsernameNotFoundException; -import org.springframework.stereotype.Service; -import org.springframework.util.StringUtils; - -import java.util.Collection; -import java.util.List; -import java.util.Set; -import java.util.HashSet; -import java.util.stream.Collectors; - -/** - * 自定义用户详情服务 - * 实现Spring Security的UserDetailsService接口 - * - * @author StdProject - */ -@Slf4j -@Service -public class CustomUserDetailsService implements UserDetailsService { - - @Autowired - private IUserService appUserService; - - @Autowired - private IRoleService appRoleService; - - - @Autowired - private IMenuService appMenuService; - - @Override - public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { - log.debug("加载用户信息: {}", username); - User appUser = appUserService.findByUsername(username); - if (appUser == null) { - log.warn("用户不存在: {}", username); - throw new UsernameNotFoundException("用户不存在: " + username); - } - - // 检查用户状态 - if (!"1".equals(appUser.getStatus())) { - log.warn("用户已被禁用: {}", username); - throw new UsernameNotFoundException("用户已被禁用: " + username); - } - - // 构建用户权限 - Collection authorities = buildUserAuthorities(appUser); - - return org.springframework.security.core.userdetails.User.builder() - .username(appUser.getUsername()) - .password(appUser.getPassword()) - .authorities(authorities) - .accountExpired(false) - .accountLocked(isAccountLocked(appUser)) - .credentialsExpired(isCredentialsExpired(appUser)) - .disabled(!"1".equals(appUser.getStatus())) - .build(); - } - - /** - * 构建用户权限 - * - * @param appUser 用户信息 - * @return 权限集合 - */ - private Collection buildUserAuthorities(User appUser) { - Set authorities = new HashSet<>(); - - try { - // 根据用户类型添加基本角色权限 - if ("0".equals(appUser.getUsertype())) { - authorities.add(new SimpleGrantedAuthority("ROLE_ADMIN")); - } else { - authorities.add(new SimpleGrantedAuthority("ROLE_USER")); - } - - // 查询用户的角色 -// QueryWrapper roleUserQuery = new QueryWrapper<>(); -// roleUserQuery.eq("userid", appUser.getId()); -// List roleUsers = appRoleUserService.list(roleUserQuery); -// -// if (!roleUsers.isEmpty()) { -// // 获取角色ID列表 -// List roleIds = roleUsers.stream() -// .map(AppRoleUser::getRoleid) -// .collect(Collectors.toList()); -// -// // 查询角色信息并添加角色权限 -// List roles = appRoleService.listByIds(roleIds); -// for (AppRole role : roles) { -// if ("1".equals(role.getIsvaild())) { -// // 添加角色权限,格式:ROLE_角色编码 -// if (StringUtils.hasText(role.getRolecode())) { -// authorities.add(new SimpleGrantedAuthority("ROLE_" + role.getRolecode().toUpperCase())); -// } -// } -// } -// -// // 查询角色关联的菜单权限 -// QueryWrapper roleMenuQuery = new QueryWrapper<>(); -// roleMenuQuery.in("roleid", roleIds); -// List roleMenus = appRoleMenuService.list(roleMenuQuery); -// -// if (!roleMenus.isEmpty()) { -// // 获取菜单ID列表 -// List menuIds = roleMenus.stream() -// .map(AppRoleMenu::getMenuid) -// .distinct() -// .collect(Collectors.toList()); -// -// // 查询菜单信息并添加菜单权限 -// List menus = appMenuService.listByIds(menuIds); -// for (AppMenu menu : menus) { -// if ("1".equals(menu.getIsdisplay()) && StringUtils.hasText(menu.getCode())) { -// // 添加菜单权限,格式:菜单编码 -// authorities.add(new SimpleGrantedAuthority(menu.getCode())); -// -// // 根据菜单类型添加操作权限 -// String menuCode = menu.getCode(); -// if (StringUtils.hasText(menuCode)) { -// // 为每个菜单添加基本操作权限 -// authorities.add(new SimpleGrantedAuthority(menuCode + ":list")); -// authorities.add(new SimpleGrantedAuthority(menuCode + ":detail")); -// -// // 管理员拥有所有操作权限 -// if ("0".equals(appUser.getUsertype())) { -// authorities.add(new SimpleGrantedAuthority(menuCode + ":add")); -// authorities.add(new SimpleGrantedAuthority(menuCode + ":edit")); -// authorities.add(new SimpleGrantedAuthority(menuCode + ":delete")); -// authorities.add(new SimpleGrantedAuthority(menuCode + ":permission")); -// } -// } -// } -// } -// } -// } - - log.debug("用户 {} 的权限列表: {}", appUser.getUsername(), - authorities.stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList())); - - } catch (Exception e) { - log.error("构建用户权限失败: {}", e.getMessage(), e); - // 发生异常时,至少保证基本角色权限 - authorities.clear(); - if ("0".equals(appUser.getUsertype())) { - authorities.add(new SimpleGrantedAuthority("ROLE_ADMIN")); - } else { - authorities.add(new SimpleGrantedAuthority("ROLE_USER")); - } - } - - return authorities; - } - - /** - * 检查账户是否被锁定 - * - * @param appUser 用户信息 - * @return 是否被锁定 - */ - private boolean isAccountLocked(User appUser) { - // 检查登录失败锁定时间 - if (appUser.getFailedlocktime() != null) { - // 如果锁定时间还未过期,则账户被锁定 - return appUser.getFailedlocktime().isAfter(java.time.LocalDateTime.now()); - } - return false; - } - - /** - * 检查凭证是否过期 - * - * @param appUser 用户信息 - * @return 是否过期 - */ - private boolean isCredentialsExpired(User appUser) { - // 检查密码是否过期 - if (appUser.getPwdresettime() != null && appUser.getPwdvalidperiod() != null) { - java.time.LocalDateTime expireTime = appUser.getPwdresettime().plusDays(appUser.getPwdvalidperiod()); - return expireTime.isBefore(java.time.LocalDateTime.now()); - } - return false; - } -} \ No newline at end of file diff --git a/backend/src/main/java/com/stdproject/config/SecurityConfig.java b/backend/src/main/java/com/stdproject/config/SecurityConfig.java index ee0ffd3..ca9cd9a 100644 --- a/backend/src/main/java/com/stdproject/config/SecurityConfig.java +++ b/backend/src/main/java/com/stdproject/config/SecurityConfig.java @@ -1,5 +1,6 @@ package com.stdproject.config; +import com.stdproject.service.CustomUserDetailsService; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; @@ -11,11 +12,9 @@ import org.springframework.security.config.annotation.method.configuration.Enabl import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.http.SessionCreationPolicy; -import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; -import org.springframework.security.web.header.writers.ReferrerPolicyHeaderWriter; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfigurationSource; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; diff --git a/backend/src/main/java/com/stdproject/config/WebConfig.java b/backend/src/main/java/com/stdproject/config/WebConfig.java index 7c80abc..50b6d7a 100644 --- a/backend/src/main/java/com/stdproject/config/WebConfig.java +++ b/backend/src/main/java/com/stdproject/config/WebConfig.java @@ -28,6 +28,10 @@ public class WebConfig implements WebMvcConfigurer { return CacheUtil.newTimedCache(jwtExpirationMs); } + @Bean + public Cache loginuserCache() { + return CacheUtil.newLRUCache(200);//用户登录缓存数 缺省200 + } /** * 静态资源处理 */ diff --git a/backend/src/main/java/com/stdproject/controller/MenuController.java b/backend/src/main/java/com/stdproject/controller/MenuController.java index f4d3348..e65dcc7 100644 --- a/backend/src/main/java/com/stdproject/controller/MenuController.java +++ b/backend/src/main/java/com/stdproject/controller/MenuController.java @@ -60,7 +60,7 @@ public class MenuController { return ResponseResult.error("没有菜单ID"); } //填写 当前用户名称 - menu.setLastmodifier(AuthUtils.getUser().getUserId().toString()); +// menu.setLastmodifier(AuthUtils.getUser().getUserId().toString()); //填写 当前日期 menu.setLastmodifydate(LocalDateTime.now()); boolean isOk = menuService.updateById(menu); @@ -82,8 +82,9 @@ public class MenuController { ***********************************/ @PostMapping("/getMenuTree") @ResponseBody - public List> getMenuTree(String appId, String name, String isdisplay) { - return menuService.getMenuTree(appId, name, isdisplay); + public ResponseResult getMenuTree(String appId, String name, String isdisplay) { + List> list= menuService.getMenuTree(appId, name, isdisplay); + return ResponseResult.successData(list); } @@ -133,7 +134,7 @@ public class MenuController { //根据id 修改是否显示 ,最近修改人,最近修改时间 updateWrapper.eq("id", id) .set("isdisplay", isdisplay) - .set("lastmodifier", AuthUtils.getUser().getUserId().toString()) +// .set("lastmodifier", AuthUtils.getUser().getUserId().toString()) .set("lastmodifydate", new Timestamp(System.currentTimeMillis())); boolean ok = menuService.update(updateWrapper); if (ok) { @@ -157,7 +158,7 @@ public class MenuController { //根据id 修改关联模块ID ,最近修改人,最近修改时间 updateWrapper.eq("id", id) .set("module_id", moduleId) - .set("lastmodifier", AuthUtils.getUser().getUserId().toString()) +// .set("lastmodifier", AuthUtils.getUser().getUserId().toString()) .set("lastmodifydate", new Timestamp(System.currentTimeMillis())); boolean ok = menuService.update(updateWrapper); if (ok) { @@ -176,8 +177,8 @@ public class MenuController { ***********************************/ @PostMapping("/permissionAssignment") @ResponseBody - public List> permissionAssignment(String appId, String roleId) { - - return menuService.permissionAssignment(appId,roleId); + public ResponseResult permissionAssignment(String appId, String roleId) { + List> list=menuService.permissionAssignment(appId,roleId); + return ResponseResult.successData( list); } } diff --git a/backend/src/main/java/com/stdproject/controller/ModuleController.java b/backend/src/main/java/com/stdproject/controller/ModuleController.java new file mode 100644 index 0000000..3e314fe --- /dev/null +++ b/backend/src/main/java/com/stdproject/controller/ModuleController.java @@ -0,0 +1,60 @@ +package com.stdproject.controller; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.stdproject.config.ResponseResult; +import com.stdproject.entity.Module; +import com.stdproject.service.IModuleService; +import jakarta.annotation.Resource; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Map; + + +/** + *

+ * 应用_系统模块 前端控制器 + *

+ * + * @author zhengsl + * @since 2025-03-25 + */ +@RestController +@RequestMapping("/module") +public class ModuleController { + // 添加Service注入 + @Resource + private IModuleService moduleService; + + // 分页查询模块列表 + @GetMapping("/list") + public ResponseResult page(@RequestParam String appId) { + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.eq("app_id",appId); + wrapper.orderByAsc("pid","sort"); // 按编码倒序排列 + List> list = moduleService.listMaps(wrapper); + return ResponseResult.successData(list); + } + + // 删除模块 + @DeleteMapping("/{id}") + public ResponseResult remove(@PathVariable String id) { + return ResponseResult.successData(moduleService.removeById(id)); + } + + // 获取模块详情 + @GetMapping("/{id}") + public ResponseResult getById(@PathVariable String id) { + Map moduleMap=null; + try { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(Module::getId, id); + moduleMap = moduleService.getMap(wrapper); + } catch (Exception e) { + e.printStackTrace(); + } + return ResponseResult.successData(moduleMap); + } + +} diff --git a/backend/src/main/java/com/stdproject/controller/OrganizationController.java b/backend/src/main/java/com/stdproject/controller/OrganizationController.java index 6847dc0..06e542c 100644 --- a/backend/src/main/java/com/stdproject/controller/OrganizationController.java +++ b/backend/src/main/java/com/stdproject/controller/OrganizationController.java @@ -69,7 +69,7 @@ public class OrganizationController { return ResponseResult.error("组织信息不能为空"); } //填写 最近修改者 - organization.setLastmodifier(AuthUtils.getUser().getUserId().toString()); +// organization.setLastmodifier(AuthUtils.getUser().getUserId().toString()); //填写 最近修改时间 organization.setLastmodifydate(LocalDateTime.now()); //根据id 修改系统组织 @@ -93,8 +93,9 @@ public class OrganizationController { ***********************************/ @PostMapping("/getOrganizations") @ResponseBody - public List> getOrganizations(String appId, String orgtype, String parentid, String orgName) { - return organizationService.getOrganizations(appId,orgtype, parentid, orgName); + public ResponseResult getOrganizations(String appId, String orgtype, String parentid, String orgName) { + List> list=organizationService.getOrganizations(appId,orgtype, parentid, orgName); + return ResponseResult.successData(list); } /*********************************** @@ -156,7 +157,7 @@ public class OrganizationController { UpdateWrapper updateWrapper = new UpdateWrapper<>(); updateWrapper.eq("id", id) .set("isvaild", isvaild) - .set("lastmodifier", AuthUtils.getUser().getUserId().toString()) +// .set("lastmodifier", AuthUtils.getUser().getUserId().toString()) .set("lastmodifydate", LocalDateTime.now()); boolean isOk = organizationService.update(updateWrapper); if (isOk) { diff --git a/backend/src/main/java/com/stdproject/controller/RoleController.java b/backend/src/main/java/com/stdproject/controller/RoleController.java index e5e76c7..7440925 100644 --- a/backend/src/main/java/com/stdproject/controller/RoleController.java +++ b/backend/src/main/java/com/stdproject/controller/RoleController.java @@ -63,7 +63,7 @@ public class RoleController { return ResponseResult.error("需要提交角色ID!"); } //更新最近修改人 - role.setLastmodifier(AuthUtils.getUser().getUserId().toString()); +// role.setLastmodifier(AuthUtils.getUser().getUserId().toString()); //更新最近修改时间 role.setLastmodifydate(LocalDateTime.now()); //根据id更新角色信息 @@ -84,8 +84,9 @@ public class RoleController { ***********************************/ @PostMapping("/listRole") @ResponseBody - public List listRole(String appId, String rolename) { - return roleService.listRole(appId,rolename); + public ResponseResult listRole(String appId, String rolename) { + List list=roleService.listRole(appId,rolename); + return ResponseResult.successData( list); } /*********************************** @@ -152,7 +153,7 @@ public class RoleController { //根据id 更新业务范围,最近修改人,最近修改时间 updateWrapper.eq("id", id) .set("isvaild", isvaild) - .set("lastmodifier", AuthUtils.getUser().getUserId().toString()) +// .set("lastmodifier", AuthUtils.getUser().getUserId().toString()) .set("lastmodifydate",LocalDateTime.now()); boolean ok = roleService.update(updateWrapper); if (ok) { diff --git a/backend/src/main/java/com/stdproject/controller/UserController.java b/backend/src/main/java/com/stdproject/controller/UserController.java index a742916..fc8c1d5 100644 --- a/backend/src/main/java/com/stdproject/controller/UserController.java +++ b/backend/src/main/java/com/stdproject/controller/UserController.java @@ -1,16 +1,42 @@ package com.stdproject.controller; -import cn.hutool.core.util.ObjUtil; +import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; -import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import cn.hutool.json.JSONUtil; +import cn.hutool.jwt.JWTUtil; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.stdproject.config.ResponseResult; +import com.stdproject.config.WebConfig; +import com.stdproject.entity.AppOptLog; +import com.stdproject.entity.LoginUser; import com.stdproject.entity.User; +import com.stdproject.service.IAppOptLogService; import com.stdproject.service.IUserService; +import com.stdproject.utils.JwtUtils; +import com.stdproject.utils.RsaUtils; +import com.stdproject.utils.commonUtils; import io.micrometer.common.util.StringUtils; import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.authentication.DisabledException; +import org.springframework.security.authentication.LockedException; +import org.springframework.security.authentication.AccountExpiredException; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.web.bind.annotation.*; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; +import java.sql.Timestamp; +import java.time.LocalDateTime; +import java.util.HashMap; import java.util.Map; @@ -28,6 +54,18 @@ public class UserController { @Resource private IUserService userService; + @Value("${spring.security.rsa.private-key}") + private String privateKey; + @Value("${spring.security.jwt.expiration-ms}") +private Long jwtExpirationMs; + @Autowired + private AuthenticationManager authenticationManager; + @Autowired + private WebConfig webConfig; + @Resource + private IAppOptLogService sysLogService; + @Autowired + private JwtUtils jwtUtils; /*********************************** * 用途说明:新增系统用户 @@ -70,7 +108,7 @@ public class UserController { ***********************************/ @GetMapping("/queryUsers") @ResponseBody - public ResponseResult queryUsers(String orgid,String appId, String nickname, Page page) { + public ResponseResult queryUsers(String orgid, String appId, String nickname, Page page) { Page> mapPage = userService.queryUsers(orgid, appId, nickname, page); return ResponseResult.successData(mapPage); } @@ -115,7 +153,6 @@ public class UserController { } - /*********************************** * 用途说明:设置账号状态(管理员) * 参数说明 @@ -125,7 +162,7 @@ public class UserController { ************************************/ @PostMapping("/setStatus") @ResponseBody - public ResponseResult setStatus(@RequestParam String id,@RequestParam String status) { + public ResponseResult setStatus(@RequestParam String id, @RequestParam String status) { if (StringUtils.isEmpty(id)) { return ResponseResult.error("用户ID为空"); } @@ -159,22 +196,91 @@ public class UserController { return ResponseResult.error(); } } + @PostMapping("/login") @ResponseBody - public ResponseResult login(String appid,String username, String password) { - if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) { - return ResponseResult.error("用户名称或者密码不能为空!"); + public ResponseResult login(String username, String password) throws Exception { + try { + // 密码解密 + String encrypt_password = RsaUtils.decryptByPrivateKey(privateKey, password); + //如果认证通过了,使用userId生成token token存入ResponseResult返回 + UsernamePasswordAuthenticationToken authenticationToken = + new UsernamePasswordAuthenticationToken(username, + encrypt_password); + Authentication authenticate = + authenticationManager.authenticate(authenticationToken); + LoginUser loginUser = (LoginUser) authenticate.getPrincipal(); + // 获取请求信息 + ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + HttpServletRequest request = attributes.getRequest(); + AppOptLog sysLog = new AppOptLog(); + sysLog.setUsercode(username); + sysLog.setUsername(loginUser.getUser().getNickname()); + sysLog.setRequestip(commonUtils.getIpAddress(request)); + sysLog.setBrowser(commonUtils.getBrowser(request)); + sysLog.setOpttype("登录(login)"); + sysLog.setModule("用户登录"); + String className = this.getClass().getName(); + String method = + Thread.currentThread().getStackTrace()[1].getMethodName(); + sysLog.setMethod(className + "." + method + "()"); + //sysLog.setParams(user.toString()); + sysLog.setDescription(loginUser.getUser().getNickname() + "登录系统!"); + sysLog.setLogtime(LocalDateTime.now()); + sysLogService.save(sysLog); + String userId = loginUser.getUser().getId(); + long expireTime = System.currentTimeMillis() + jwtExpirationMs; + Map map = new HashMap(); + map.put("userid", userId); + map.put("username", loginUser.getUsername()); + map.put("expire_time", expireTime); + String token = jwtUtils.generateToken(loginUser.getUsername(), userId); + map.put("token", token); + //把完整的用户信息存入到HuTool缓存中,userId作为key + String jsonStr = JSONUtil.toJsonStr(loginUser); + webConfig.loginuserCache().put("login:" + userId, jsonStr); + return ResponseResult.successData(map); } - // 根据用户名查询用户 - QueryWrapper queryWrapper = new QueryWrapper<>(); - User user = userService.getOne(queryWrapper.eq("app_id",appid).eq("username", username)); - if (ObjUtil.isEmpty( user)) { - return ResponseResult.error(String.format("%s您输入的用户账号不存在!", username)); - } - if(password.equals(user.getPassword())){ - return ResponseResult.successData(user); - }else{ - return ResponseResult.error("您输入的密码错误!"); + catch (BadCredentialsException e) { + // 账号密码验证错误 + return ResponseResult.error("账号或密码错误,请检查!"); + } catch (DisabledException e) { + // 账号已停用 + return ResponseResult.error("账号已停用,不能登录!"); + } catch (LockedException e) { + // 账号被锁定 + return ResponseResult.error("账号被锁定,不能登录!"); + } catch (AccountExpiredException e) { + // 账号已过期 + return ResponseResult.error("账号密码已过有效期,不能登录!"); + } catch (AuthenticationException e) { + // 捕获其他认证异常 + return ResponseResult.error("认证失败:" + e.getMessage()); } } + @PostMapping("/logout") + @ResponseBody + public ResponseResult logout() { + //获取SecurityContextHolder中的用户id + UsernamePasswordAuthenticationToken authentication = + (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication(); + LoginUser loginuser = (LoginUser) authentication.getPrincipal(); + String userId = loginuser.getUser().getId(); + //删除redis中的登陆用户信息 + webConfig.loginuserCache().remove("login:" + userId); + //记录退出日志 + ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + HttpServletRequest request = attributes.getRequest(); + AppOptLog sysLog = new AppOptLog(); + sysLog.setUsercode(loginuser.getUsername()); + sysLog.setUsername(loginuser.getUser().getNickname()); + sysLog.setRequestip(commonUtils.getIpAddress(request)); + sysLog.setBrowser(commonUtils.getBrowser(request)); + sysLog.setOpttype("其他(other)"); + sysLog.setModule("注销退出"); + sysLog.setDescription("注销退出系统!"); + sysLog.setLogtime(LocalDateTime.now()); + sysLogService.save(sysLog); + return ResponseResult.success(); + } } diff --git a/backend/src/main/java/com/stdproject/entity/Application.java b/backend/src/main/java/com/stdproject/entity/Application.java new file mode 100644 index 0000000..59dad39 --- /dev/null +++ b/backend/src/main/java/com/stdproject/entity/Application.java @@ -0,0 +1,109 @@ +package com.stdproject.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Getter; +import lombok.Setter; + +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + *

+ * 应用系统 + *

+ * + * @author zhengsl + * @since 2025-03-24 + */ +@Getter +@Setter +@TableName("app_application") +public class Application implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * ID + */ + private String id; + + /** + * 应用类型 01-数据展示分析 + */ + private String type; + + /** + * 应用编号 自动生成:000001 + */ + private String code; + + /** + * 应用名称 + */ + private String name; + + /** + * 应用简称 + */ + private String simplename; + + /** + * 创建日期 + */ + private LocalDateTime createdate; + + /** + * 应用描述 + */ + private String description; + + /** + * 应用图片 base64存储 + */ + private String image; + + /** + * 应用配置 JSON格式存储其他配置相关信息 + */ + private String appconfig; + + /** + * 数据库配置 JSON格式存储数据库信息配置(数据库用于存储此应用系统的所有配置数据) + */ + private String dbconfig; + + /** + * 用户单位 使用这个应用系统的组织单位 + */ + private String organization; + + /** + * 状态 01-初始创建 02-正常运行 09-已停用 + */ + private String status; + + /** + * 最近修改者 + */ + private String lastmodifier; + + /** + * 最近修改日期 + */ + private LocalDateTime lastmodifydate; + + /** + * 备用1 + */ + private String custom1; + + /** + * 备用2 + */ + private String custom2; + + /** + * 备用3 + */ + private String custom3; +} diff --git a/backend/src/main/java/com/stdproject/entity/Module.java b/backend/src/main/java/com/stdproject/entity/Module.java new file mode 100644 index 0000000..968ddd9 --- /dev/null +++ b/backend/src/main/java/com/stdproject/entity/Module.java @@ -0,0 +1,178 @@ +package com.stdproject.entity; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Getter; +import lombok.Setter; +import org.apache.ibatis.type.JdbcType; + +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + *

+ * 应用_系统模块 + *

+ * + * @author zhengsl + * @since 2025-03-25 + */ +@Getter +@Setter +@TableName("app_module") +public class Module implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 主键 + */ + private String id; + + /** + * 应用ID 关联应用系统 + */ + private String appId; + + /** + * 名称 + */ + private String name; + + /** + * 父级ID 父级模块 + */ + @TableField(value = "pid", jdbcType = JdbcType.VARCHAR) + private String pid; + + /** + * 层级 + */ + private Integer level; + + /** + * 节点类型 01-目录 02-模块 + */ + private String nodeType; + + /** + * 类型 01-数据大屏 02-数据看板 03-弹窗页面 04-填报页面 06-移动端界面 09-自定义页面 + */ + private String type; + + /** + * 样式数据 + */ + @TableField(value = "canvas_style_data", jdbcType = JdbcType.VARCHAR) + private String canvasStyleData; + + /** + * 组件数据 + */ + @TableField(value = "component_data", jdbcType = JdbcType.VARCHAR) + private String componentData; + + /** + * 状态 0-未发布 1-已发布 + */ + private Integer status; + + /** + * 是否单独打开水印 0-关闭 1-开启 + */ + private Integer selfWatermarkStatus; + + /** + * 排序 目录内的排序号 + */ + private Integer sort; + + /** + * 创建时间 + */ + private LocalDateTime createTime; + + /** + * 创建人 + */ + private String createBy; + + /** + * 更新时间 + */ + private LocalDateTime updateTime; + + /** + * 更新人 + */ + private String updateBy; + + /** + * 备注 + */ + private String remark; + + /** + * 数据来源 + */ + private String source; + + /** + * 删除标志 + */ + private Byte deleteFlag; + + /** + * 删除时间 + */ + private LocalDateTime deleteTime; + + /** + * 删除人 + */ + private String deleteBy; + + /** + * 可视化资源版本 + */ + private Integer version; + + /** + * 内容标识 + */ + private String contentId; + + /** + * 内容检查标识 + */ + private String checkVersion; + + /** + * 备用1 + */ + private String custom1; + + /** + * 备用2 + */ + private String custom2; + + /** + * 备用3 + */ + private String custom3; + + public Module(String appId, String name, String pid, Integer level, String nodeType, String type, Integer status, Integer sort, LocalDateTime createTime, String createBy){ + + this.appId = appId; + this.name = name; + this.pid = pid; + this.level = level; + this.nodeType = nodeType; + this.type = type; + this.status = status; + this.sort = sort; + this.createTime = createTime; + this.createBy = createBy; + }; +} diff --git a/backend/src/main/java/com/stdproject/mapper/ApplicationMapper.java b/backend/src/main/java/com/stdproject/mapper/ApplicationMapper.java new file mode 100644 index 0000000..68ab2b9 --- /dev/null +++ b/backend/src/main/java/com/stdproject/mapper/ApplicationMapper.java @@ -0,0 +1,21 @@ +package com.stdproject.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +import com.stdproject.entity.Application; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Select; + +/** + *

+ * 应用系统 Mapper 接口 + *

+ * + * @author zhengsl + * @since 2025-03-24 + */ +@Mapper +public interface ApplicationMapper extends BaseMapper { + @Select("SELECT MAX(CAST(code AS UNSIGNED)) FROM app_application") + Integer getMaxCode(); +} diff --git a/backend/src/main/java/com/stdproject/mapper/ModuleMapper.java b/backend/src/main/java/com/stdproject/mapper/ModuleMapper.java new file mode 100644 index 0000000..734c0bf --- /dev/null +++ b/backend/src/main/java/com/stdproject/mapper/ModuleMapper.java @@ -0,0 +1,17 @@ +package com.stdproject.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; +import com.stdproject.entity.Module; +/** + *

+ * 应用_系统模块 Mapper 接口 + *

+ * + * @author zhengsl + * @since 2025-03-25 + */ +@Mapper +public interface ModuleMapper extends BaseMapper { + +} diff --git a/backend/src/main/java/com/stdproject/service/CustomUserDetailsService.java b/backend/src/main/java/com/stdproject/service/CustomUserDetailsService.java new file mode 100644 index 0000000..d48b2fc --- /dev/null +++ b/backend/src/main/java/com/stdproject/service/CustomUserDetailsService.java @@ -0,0 +1,147 @@ +package com.stdproject.service; + +import com.stdproject.entity.LoginUser; +import com.stdproject.entity.Menu; +import com.stdproject.entity.Role; +import com.stdproject.entity.User; +import com.stdproject.mapper.MenuMapper; +import com.stdproject.mapper.RoleMapper; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * 自定义用户详情服务 + * 实现Spring Security的UserDetailsService接口 + * + * @author StdProject + */ +@Slf4j +@Service +public class CustomUserDetailsService implements UserDetailsService { + + @Autowired + private IUserService appUserService; + + + @Autowired + private IMenuService appMenuService; + + @Autowired + private RoleMapper roleMapper; + + @Autowired + private MenuMapper menuMapper; + + @Override + public UserDetails loadUserByUsername(String username) { + User appUser = appUserService.findByUsername(username); + if (appUser == null) { + throw new UsernameNotFoundException("用户不存在: " + username); + } + Collection authorities = buildUserAuthorities(appUser); + LoginUser loginUser = new LoginUser(appUser,authorities); + return loginUser; + + } + + /** + * 构建用户权限 + * + * @param appUser 用户信息 + * @return 权限集合 + */ + private Collection buildUserAuthorities(User appUser) { + Set authorities = new HashSet<>(); + + try { + // 根据用户类型添加基本角色权限 + if ("0".equals(appUser.getUsertype())) { + authorities.add(new SimpleGrantedAuthority("ROLE_ADMIN")); + } else { + authorities.add(new SimpleGrantedAuthority("ROLE_USER")); + } + + // 使用RoleMapper直接查询用户的角色信息 + List roles = roleMapper.getRoleByUserId(appUser.getId()); + + if (!roles.isEmpty()) { + // 处理角色权限 + for (Role role : roles) { + if ("1".equals(role.getIsvaild())) { + // 添加角色权限,格式:ROLE_角色编码 + if (StringUtils.hasText(role.getRolecode())) { + authorities.add(new SimpleGrantedAuthority("ROLE_" + role.getRolecode().toUpperCase())); + } + } + } + // 获取角色ID列表 + List roleIds = roles.stream() + .map(Role::getId) + .collect(Collectors.toList()); + + // 处理每个角色的菜单权限 + for (String roleId : roleIds) { + // 使用MenuMapper直接查询角色关联的菜单ID + List menuIds = menuMapper.selectMenuByRoleId(appUser.getAppId(), roleId); + + if (!menuIds.isEmpty()) { + // 查询菜单信息并添加菜单权限 + List menus = appMenuService.listByIds(menuIds); + for (Menu menu : menus) { + if ("1".equals(menu.getIsdisplay()) && StringUtils.hasText(menu.getCode())) { + // 添加菜单权限,格式:菜单编码 + authorities.add(new SimpleGrantedAuthority(menu.getCode())); + + // 根据菜单类型添加操作权限 + String menuCode = menu.getCode(); + if (StringUtils.hasText(menuCode)) { + // 为每个菜单添加基本操作权限 + authorities.add(new SimpleGrantedAuthority(menuCode + ":list")); + authorities.add(new SimpleGrantedAuthority(menuCode + ":detail")); + + // 管理员拥有所有操作权限 + if ("0".equals(appUser.getUsertype())) { + authorities.add(new SimpleGrantedAuthority(menuCode + ":add")); + authorities.add(new SimpleGrantedAuthority(menuCode + ":edit")); + authorities.add(new SimpleGrantedAuthority(menuCode + ":delete")); + authorities.add(new SimpleGrantedAuthority(menuCode + ":permission")); + } + } + } + } + } + } + } + + log.debug("用户 {} 的权限列表: {}", appUser.getUsername(), + authorities.stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList())); + + } catch (Exception e) { + log.error("构建用户权限失败: {}", e.getMessage(), e); + // 发生异常时,至少保证基本角色权限 + authorities.clear(); + if ("0".equals(appUser.getUsertype())) { + authorities.add(new SimpleGrantedAuthority("ROLE_ADMIN")); + } else { + authorities.add(new SimpleGrantedAuthority("ROLE_USER")); + } + } + + return authorities; + } + + +} \ No newline at end of file diff --git a/backend/src/main/java/com/stdproject/service/IApplicationService.java b/backend/src/main/java/com/stdproject/service/IApplicationService.java new file mode 100644 index 0000000..7ca1aed --- /dev/null +++ b/backend/src/main/java/com/stdproject/service/IApplicationService.java @@ -0,0 +1,16 @@ +package com.stdproject.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.stdproject.entity.Application; + + +/** + *

+ * 应用系统 服务类 + *

+ * + * @author zhengsl + * @since 2025-03-24 + */ +public interface IApplicationService extends IService { +} diff --git a/backend/src/main/java/com/stdproject/service/IModuleService.java b/backend/src/main/java/com/stdproject/service/IModuleService.java new file mode 100644 index 0000000..1dd8dfe --- /dev/null +++ b/backend/src/main/java/com/stdproject/service/IModuleService.java @@ -0,0 +1,15 @@ +package com.stdproject.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.stdproject.entity.Module; +/** + *

+ * 应用_系统模块 服务类 + *

+ * + * @author zhengsl + * @since 2025-03-25 + */ +public interface IModuleService extends IService { + +} diff --git a/backend/src/main/java/com/stdproject/service/impl/ApplicationServiceImpl.java b/backend/src/main/java/com/stdproject/service/impl/ApplicationServiceImpl.java new file mode 100644 index 0000000..8cc9780 --- /dev/null +++ b/backend/src/main/java/com/stdproject/service/impl/ApplicationServiceImpl.java @@ -0,0 +1,20 @@ +package com.stdproject.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.stdproject.entity.Application; +import com.stdproject.mapper.ApplicationMapper; +import com.stdproject.service.IApplicationService; +import org.springframework.stereotype.Service; + +/** + *

+ * 应用系统 服务实现类 + *

+ * + * @author zhengsl + * @since 2025-03-24 + */ +@Service +public class ApplicationServiceImpl extends ServiceImpl implements IApplicationService { + +} diff --git a/backend/src/main/java/com/stdproject/service/impl/ModuleServiceImpl.java b/backend/src/main/java/com/stdproject/service/impl/ModuleServiceImpl.java new file mode 100644 index 0000000..a919aef --- /dev/null +++ b/backend/src/main/java/com/stdproject/service/impl/ModuleServiceImpl.java @@ -0,0 +1,21 @@ +package com.stdproject.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.stdproject.entity.Module; +import com.stdproject.mapper.ModuleMapper; +import com.stdproject.service.IModuleService; +import org.springframework.stereotype.Service; + +/** + *

+ * 应用_系统模块 服务实现类 + *

+ * + * @author zhengsl + * @since 2025-03-25 + */ +@Service +public class ModuleServiceImpl extends ServiceImpl implements IModuleService { + + +} \ No newline at end of file diff --git a/backend/src/main/java/com/stdproject/service/impl/OrganizationServiceImpl.java b/backend/src/main/java/com/stdproject/service/impl/OrganizationServiceImpl.java index 3a6da7e..a3dfaf8 100644 --- a/backend/src/main/java/com/stdproject/service/impl/OrganizationServiceImpl.java +++ b/backend/src/main/java/com/stdproject/service/impl/OrganizationServiceImpl.java @@ -166,7 +166,7 @@ public class OrganizationServiceImpl extends ServiceImpl implements IR role.setIsvaild("1"); } //添加最近修改者 - role.setLastmodifier(AuthUtils.getUser().getUserId().toString()); +// role.setLastmodifier(AuthUtils.getUser().getUserId().toString()); //添加最近修改时间 role.setLastmodifydate(LocalDateTime.now()); return this.save(role); diff --git a/backend/src/main/java/com/stdproject/service/impl/UserServiceImpl.java b/backend/src/main/java/com/stdproject/service/impl/UserServiceImpl.java index ebd6a86..9d6d9b7 100644 --- a/backend/src/main/java/com/stdproject/service/impl/UserServiceImpl.java +++ b/backend/src/main/java/com/stdproject/service/impl/UserServiceImpl.java @@ -12,6 +12,7 @@ import com.stdproject.entity.User; // 确保 User 类存在并路径正确 import com.stdproject.mapper.RoleMapper; import com.stdproject.mapper.UserMapper; import com.stdproject.service.IUserService; // 确保接口存在 +import com.stdproject.utils.EncryptUtils; import io.gisbi.utils.AuthUtils; import io.gisbi.utils.RsaUtils; import io.micrometer.common.util.StringUtils; @@ -64,12 +65,12 @@ public class UserServiceImpl extends ServiceImpl implements IU Map result = new HashMap<>(); //普通用户 //设置缺省密码 - String cryptPassword = RsaUtils.encryptStr(PASSWORD_VALID); + String cryptPassword = EncryptUtils.md5(PASSWORD_VALID);; user.setPassword(cryptPassword); //最近修改日期 user.setLastmodifydate(LocalDateTime.now()); //最近修改者 - user.setLastmodifier(AuthUtils.getUser().getUserId().toString()); +// user.setLastmodifier(AuthUtils.getUser().getUserId().toString()); //账号有效 状态 1-有效 0-停用 user.setStatus(DEFAULT_IS_VALID); //判断注册的登录账号是否存在 @@ -274,8 +275,8 @@ public class UserServiceImpl extends ServiceImpl implements IU String cryptPassword = PASSWORD_VALID; updateWrapper.eq("id", id).set("password", cryptPassword) .set("pwdresettime", LocalDateTime.now()) - .set("lastmodifydate", LocalDateTime.now()) - .set("lastmodifier", AuthUtils.getUser().getUserId().toString()); + .set("lastmodifydate", LocalDateTime.now()); +// .set("lastmodifier", AuthUtils.getUser().getUserId().toString()); //是否修改成功 isOk = this.update(updateWrapper); return isOk; @@ -295,8 +296,8 @@ public class UserServiceImpl extends ServiceImpl implements IU //根据id修改用户状态,最近修改人,最近修改时间 updateWrapper.eq("id", id) .set("status", status) - .set("lastmodifydate", LocalDateTime.now()) - .set("lastmodifier", AuthUtils.getUser().getUserId().toString()); + .set("lastmodifydate", LocalDateTime.now()); +// .set("lastmodifier", AuthUtils.getUser().getUserId().toString()); //是否修改成功 isOk = this.update(updateWrapper); return isOk; diff --git a/backend/src/main/java/com/stdproject/utils/RsaUtils.java b/backend/src/main/java/com/stdproject/utils/RsaUtils.java new file mode 100644 index 0000000..805d29f --- /dev/null +++ b/backend/src/main/java/com/stdproject/utils/RsaUtils.java @@ -0,0 +1,183 @@ +package com.stdproject.utils; + +import org.apache.commons.codec.binary.Base64; + +import javax.crypto.Cipher; +import java.security.*; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; + +/** + * @author https://www.cnblogs.com/nihaorz/p/10690643.html + * @description Rsa 工具类,公钥私钥生成,加解密 + * @date 2020-05-18 + **/ +public class RsaUtils { + + private static final String SRC = "123456"; + + public static void main(String[] args) throws Exception { + System.out.println("\n"); + RsaKeyPair keyPair = generateKeyPair(); + System.out.println("公钥:" + keyPair.getPublicKey()); + System.out.println("私钥:" + keyPair.getPrivateKey()); + System.out.println("\n"); + test1(keyPair); + System.out.println("\n"); + test2(keyPair); + System.out.println("\n"); + + + } + + /** + * 公钥加密私钥解密 + */ + private static void test1(RsaKeyPair keyPair) throws Exception { + System.out.println("***************** 公钥加密私钥解密开始 *****************"); + String text1 = encryptByPublicKey(keyPair.getPublicKey(), RsaUtils.SRC); + String text2 = decryptByPrivateKey(keyPair.getPrivateKey(), text1); + System.out.println("加密前:" + RsaUtils.SRC); + System.out.println("加密后:" + text1); + System.out.println("解密后:" + text2); + if (RsaUtils.SRC.equals(text2)) { + System.out.println("解密字符串和原始字符串一致,解密成功"); + } else { + System.out.println("解密字符串和原始字符串不一致,解密失败"); + } + System.out.println("***************** 公钥加密私钥解密结束 *****************"); + } + + /** + * 私钥加密公钥解密 + * @throws Exception / + */ + private static void test2(RsaKeyPair keyPair) throws Exception { + System.out.println("***************** 私钥加密公钥解密开始 *****************"); + String text1 = encryptByPrivateKey(keyPair.getPrivateKey(), RsaUtils.SRC); + String text2 = decryptByPublicKey(keyPair.getPublicKey(), text1); + System.out.println("加密前:" + RsaUtils.SRC); + System.out.println("加密后:" + text1); + System.out.println("解密后:" + text2); + if (RsaUtils.SRC.equals(text2)) { + System.out.println("解密字符串和原始字符串一致,解密成功"); + } else { + System.out.println("解密字符串和原始字符串不一致,解密失败"); + } + System.out.println("***************** 私钥加密公钥解密结束 *****************"); + } + + /** + * 公钥解密 + * + * @param publicKeyText 公钥 + * @param text 待解密的信息 + * @return / + * @throws Exception / + */ + public static String decryptByPublicKey(String publicKeyText, String text) throws Exception { + X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(Base64.decodeBase64(publicKeyText)); + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec); + Cipher cipher = Cipher.getInstance("RSA"); + cipher.init(Cipher.DECRYPT_MODE, publicKey); + byte[] result = cipher.doFinal(Base64.decodeBase64(text)); + return new String(result); + } + + /** + * 私钥加密 + * + * @param privateKeyText 私钥 + * @param text 待加密的信息 + * @return / + * @throws Exception / + */ + public static String encryptByPrivateKey(String privateKeyText, String text) throws Exception { + PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKeyText)); + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec); + Cipher cipher = Cipher.getInstance("RSA"); + cipher.init(Cipher.ENCRYPT_MODE, privateKey); + byte[] result = cipher.doFinal(text.getBytes()); + return Base64.encodeBase64String(result); + } + + /** + * 私钥解密 + * + * @param privateKeyText 私钥 + * @param text 待解密的文本 + * @return / + * @throws Exception / + */ + public static String decryptByPrivateKey(String privateKeyText, String text) throws Exception { + PKCS8EncodedKeySpec pkcs8EncodedKeySpec5 = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKeyText)); + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec5); + Cipher cipher = Cipher.getInstance("RSA"); + cipher.init(Cipher.DECRYPT_MODE, privateKey); + byte[] result = cipher.doFinal(Base64.decodeBase64(text)); + return new String(result); + } + + /** + * 公钥加密 + * + * @param publicKeyText 公钥 + * @param text 待加密的文本 + * @return / + */ + public static String encryptByPublicKey(String publicKeyText, String text) throws Exception { + X509EncodedKeySpec x509EncodedKeySpec2 = new X509EncodedKeySpec(Base64.decodeBase64(publicKeyText)); + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec2); + Cipher cipher = Cipher.getInstance("RSA"); + cipher.init(Cipher.ENCRYPT_MODE, publicKey); + byte[] result = cipher.doFinal(text.getBytes()); + return Base64.encodeBase64String(result); + } + + /** + * 构建RSA密钥对 + * + * @return / + * @throws NoSuchAlgorithmException / + */ + public static RsaKeyPair generateKeyPair() throws NoSuchAlgorithmException { + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); + keyPairGenerator.initialize(1024); + KeyPair keyPair = keyPairGenerator.generateKeyPair(); + RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic(); + RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate(); + String publicKeyString = Base64.encodeBase64String(rsaPublicKey.getEncoded()); + String privateKeyString = Base64.encodeBase64String(rsaPrivateKey.getEncoded()); + return new RsaKeyPair(publicKeyString, privateKeyString); + } + + + /** + * RSA密钥对对象 + */ + public static class RsaKeyPair { + + private final String publicKey; + private final String privateKey; + + public RsaKeyPair(String publicKey, String privateKey) { + this.publicKey = publicKey; + this.privateKey = privateKey; + } + + public String getPublicKey() { + return publicKey; + } + + public String getPrivateKey() { + return privateKey; + } + + } +} diff --git a/backend/src/main/java/com/stdproject/utils/commonUtils.java b/backend/src/main/java/com/stdproject/utils/commonUtils.java new file mode 100644 index 0000000..693eb7d --- /dev/null +++ b/backend/src/main/java/com/stdproject/utils/commonUtils.java @@ -0,0 +1,38 @@ +package com.stdproject.utils; + +import cn.hutool.http.useragent.Browser; +import cn.hutool.http.useragent.UserAgent; +import cn.hutool.http.useragent.UserAgentUtil; +import jakarta.servlet.http.HttpServletRequest; + +public class commonUtils { + + public static String getBrowser(HttpServletRequest request) { + UserAgent userAgent = UserAgentUtil.parse(request.getHeader("User-Agent")); + Browser browser = userAgent.getBrowser(); + return browser.getName(); + } + + /** + * 获取客户端IP地址 + */ + public static String getIpAddress(HttpServletRequest request) { + String ip = request.getHeader("X-Forwarded-For"); + 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.getHeader("HTTP_CLIENT_IP"); + } + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("HTTP_X_FORWARDED_FOR"); + } + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { + ip = request.getRemoteAddr(); + } + return ip; + } +} diff --git a/backend/src/main/resources/.env b/backend/src/main/resources/.env index ee60068..ded0778 100644 --- a/backend/src/main/resources/.env +++ b/backend/src/main/resources/.env @@ -12,4 +12,7 @@ JWT_EXPIRATION=86400000 SPRING_PROFILES_ACTIVE=dev # CORS配置 -CORS_ALLOWED_ORIGINS=https://your-frontend.com \ No newline at end of file +CORS_ALLOWED_ORIGINS=https://your-frontend.com + +# RSA密钥 +RSA_PRIVATE_KEY = MIIBUwIBADANBgkqhkiG9w0BAQEFAASCAT0wggE5AgEAAkEA0vfvyTdGJkdbHkB8mp0f3FE0GYP3AYPaJF7jUd1M0XxFSE2ceK3k2kw20YvQ09NJKk+OMjWQl9WitG9pB6tSCQIDAQABAkA2SimBrWC2/wvauBuYqjCFwLvYiRYqZKThUS3MZlebXJiLB+Ue/gUifAAKIg1avttUZsHBHrop4qfJCwAI0+YRAiEA+W3NK/RaXtnRqmoUUkb59zsZUBLpvZgQPfj1MhyHDz0CIQDYhsAhPJ3mgS64NbUZmGWuuNKp5coY2GIj/zYDMJp6vQIgUueLFXv/eZ1ekgz2Oi67MNCk5jeTF2BurZqNLR3MSmUCIFT3Q6uHMtsB9Eha4u7hS31tj1UWE+D+ADzp59MGnoftAiBeHT7gDMuqeJHPL4b+kC+gzV4FGTfhR9q3tTbklZkD2A== \ No newline at end of file diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml index 9f9acdd..bf86952 100644 --- a/backend/src/main/resources/application.yml +++ b/backend/src/main/resources/application.yml @@ -46,6 +46,8 @@ spring: secret: ${JWT_SECRET:YourJWTSecretKeyForStdProjectBackendApplicationWhichIsVeryLongAndSecure2024!@#$%^&*()} expiration-ms: ${JWT_EXPIRATION:86400000} # Token 过期时间 (例如: 24小时) refresh-expiration-ms: ${JWT_REFRESH_EXPIRATION:604800000} # 刷新Token过期时间 (例如: 7天) + rsa: + private-key: ${RSA_PRIVATE_KEY:MIIBUwIBADANBgkqhkiG9w0BAQEFAASCAT0wggE5AgEAAkEA0vfvyTdGJkdbHkB8mp0f3FE0GYP3AYPaJF7jUd1M0XxFSE2ceK3k2kw20YvQ09NJKk+OMjWQl9WitG9pB6tSCQIDAQABAkA2SimBrWC2/wvauBuYqjCFwLvYiRYqZKThUS3MZlebXJiLB+Ue/gUifAAKIg1avttUZsHBHrop4qfJCwAI0+YRAiEA+W3NK/RaXtnRqmoUUkb59zsZUBLpvZgQPfj1MhyHDz0CIQDYhsAhPJ3mgS64NbUZmGWuuNKp5coY2GIj/zYDMJp6vQIgUueLFXv/eZ1ekgz2Oi67MNCk5jeTF2BurZqNLR3MSmUCIFT3Q6uHMtsB9Eha4u7hS31tj1UWE+D+ADzp59MGnoftAiBeHT7gDMuqeJHPL4b+kC+gzV4FGTfhR9q3tTbklZkD2A==} mybatis-plus: mapper-locations: classpath*:/mapper/**/*.xml # MyBatis Mapper XML文件位置 diff --git a/frontend/components.d.ts b/frontend/components.d.ts index d8da8b6..c6b7ce9 100644 --- a/frontend/components.d.ts +++ b/frontend/components.d.ts @@ -9,55 +9,11 @@ export {} declare module '@vue/runtime-core' { export interface GlobalComponents { - ElBreadcrumb: typeof import('element-plus-secondary/es')['ElBreadcrumb'] - ElBreadcrumbItem: typeof import('element-plus-secondary/es')['ElBreadcrumbItem'] - ElButton: typeof import('element-plus-secondary/es')['ElButton'] ElCard: typeof import('element-plus-secondary/es')['ElCard'] - ElCheckbox: typeof import('element-plus-secondary/es')['ElCheckbox'] - ElCheckboxGroup: typeof import('element-plus-secondary/es')['ElCheckboxGroup'] - ElCol: typeof import('element-plus-secondary/es')['ElCol'] - ElCollapse: typeof import('element-plus-secondary/es')['ElCollapse'] - ElCollapseItem: typeof import('element-plus-secondary/es')['ElCollapseItem'] - ElColorPicker: typeof import('element-plus-secondary/es')['ElColorPicker'] - ElContainer: typeof import('element-plus-secondary/es')['ElContainer'] - ElDatePicker: typeof import('element-plus-secondary/es')['ElDatePicker'] - ElDialog: typeof import('element-plus-secondary/es')['ElDialog'] - ElDivider: typeof import('element-plus-secondary/es')['ElDivider'] - ElDropdown: typeof import('element-plus-secondary/es')['ElDropdown'] - ElDropdownItem: typeof import('element-plus-secondary/es')['ElDropdownItem'] - ElDropdownMenu: typeof import('element-plus-secondary/es')['ElDropdownMenu'] - ElEmpty: typeof import('element-plus-secondary/es')['ElEmpty'] - ElForm: typeof import('element-plus-secondary/es')['ElForm'] - ElFormItem: typeof import('element-plus-secondary/es')['ElFormItem'] - ElHeader: typeof import('element-plus-secondary/es')['ElHeader'] ElIcon: typeof import('element-plus-secondary/es')['ElIcon'] - ElInput: typeof import('element-plus-secondary/es')['ElInput'] - ElInputNumber: typeof import('element-plus-secondary/es')['ElInputNumber'] - ElMain: typeof import('element-plus-secondary/es')['ElMain'] - ElOption: typeof import('element-plus-secondary/es')['ElOption'] - ElOptionGroup: typeof import('element-plus-secondary/es')['ElOptionGroup'] - ElPopover: typeof import('element-plus-secondary/es')['ElPopover'] - ElRadio: typeof import('element-plus-secondary/es')['ElRadio'] - ElRadioGroup: typeof import('element-plus-secondary/es')['ElRadioGroup'] - ElRow: typeof import('element-plus-secondary/es')['ElRow'] - ElScrollbar: typeof import('element-plus-secondary/es')['ElScrollbar'] - ElSelect: typeof import('element-plus-secondary/es')['ElSelect'] - ElSelectV2: typeof import('element-plus-secondary/es')['ElSelectV2'] - ElSpace: typeof import('element-plus-secondary/es')['ElSpace'] - ElSwitch: typeof import('element-plus-secondary/es')['ElSwitch'] - ElTabPane: typeof import('element-plus-secondary/es')['ElTabPane'] - ElTabs: typeof import('element-plus-secondary/es')['ElTabs'] ElTimeline: typeof import('element-plus-secondary/es')['ElTimeline'] ElTimelineItem: typeof import('element-plus-secondary/es')['ElTimelineItem'] - ElTimePicker: typeof import('element-plus-secondary/es')['ElTimePicker'] - ElTooltip: typeof import('element-plus-secondary/es')['ElTooltip'] - ElTree: typeof import('element-plus-secondary/es')['ElTree'] - ElTreeSelect: typeof import('element-plus-secondary/es')['ElTreeSelect'] - ElUpload: typeof import('element-plus-secondary/es')['ElUpload'] RouterLink: typeof import('vue-router')['RouterLink'] RouterView: typeof import('vue-router')['RouterView'] } - export interface ComponentCustomProperties { - vLoading: typeof import('element-plus-secondary/es')['ElLoadingDirective'] - } } diff --git a/frontend/package.json b/frontend/package.json index d982475..84455b4 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -11,63 +11,65 @@ "format": "prettier --write src/" }, "dependencies": { - "video.js": "^7.21.6", - "@videojs-player/vue": "^1.0.0", - "@vueuse/core": "^9.13.0", - "@npkg/tinymce-plugins": "^0.0.7", - "dayjs": "^1.11.9", - "flv.js": "^1.6.2", - "html2canvas": "^1.4.1", - "jspdf": "^2.5.1", - "html-to-image": "^1.11.11", - "echarts": "^5.5.1", - "file-saver": "^2.0.5", - "lodash": "^4.17.21", - "lodash-es": "^4.17.21", - "mathjs": "^11.6.0", "@antv/g2plot": "^2.4.29", "@antv/l7": "^2.22.0", "@antv/l7plot": "^0.5.5", "@antv/s2": "^1.49.0", - "vue-i18n": "^9.2.2", "@element-plus/icons-vue": "^2.3.1", "@form-create/designer": "^1.1.9", "@form-create/element-ui": "^3.2.22", "@form-create/vant": "^3.2.24", - "@turf/centroid": "^7.0.0", + "@npkg/tinymce-plugins": "^0.0.7", "@tinymce/tinymce-vue": "^5.1.0", - "tinymce": "^5.8.2", + "@turf/centroid": "^7.0.0", + "@videojs-player/vue": "^1.0.0", + "@vueuse/core": "^9.13.0", "axios": "^1.6.0", "consola": "^3.4.2", + "dayjs": "^1.11.9", + "echarts": "^5.5.1", "element-plus": "^2.4.4", "element-plus-secondary": "^0.6.8", "element-resize-detector": "^1.2.4", - "vuedraggable": "^4.1.0", "exceljs": "^4.4.0", + "file-saver": "^2.0.5", + "flv.js": "^1.6.2", + "html-to-image": "^1.11.11", + "html2canvas": "^1.4.1", "js-base64": "^3.7.5", + "jsencrypt": "^3.3.2", + "jspdf": "^2.5.1", + "lodash": "^4.17.21", + "lodash-es": "^4.17.21", + "mathjs": "^11.6.0", "mitt": "^3.0.0", "path": "^0.12.7", "pinia": "^2.1.7", "qs": "^6.11.0", "screenfull": "^6.0.2", "snowflake-id": "^1.1.0", + "tinymce": "^5.8.2", "vant": "^4.9.19", + "video.js": "^7.21.6", "vue": "^3.3.4", + "vue-i18n": "^9.2.2", "vue-router": "^4.2.5", - "web-storage-cache": "^1.1.1", - "vue-types": "^5.0.2" + "vue-types": "^5.0.2", + "vue3-sfc-loader": "^0.9.5", + "vuedraggable": "^4.1.0", + "web-storage-cache": "^1.1.1" }, "devDependencies": { - "@intlify/unplugin-vue-i18n": "^0.8.2", "@rushstack/eslint-patch": "^1.3.3", "@vitejs/plugin-vue": "^4.5.2", "@vitejs/plugin-vue-jsx": "^4.2.0", "@vue/eslint-config-prettier": "^8.0.0", - "less": "^4.1.3", - "jquery": "^3.6.4", + "consola": "^2.15.3", "eslint": "^8.49.0", "eslint-plugin-vue": "^9.17.0", + "jquery": "^3.6.4", + "less": "^4.1.3", "prettier": "^3.0.3", "sass": "^1.89.0", "sass-loader": "^16.0.5", @@ -76,7 +78,6 @@ "unplugin-vue-components-secondary": "^0.24.6", "vite": "^5.0.8", "vite-plugin-style-import-secondary": "^2.0.0", - "consola": "^2.15.3", "vite-svg-loader": "^5.1.0" }, "engines": { diff --git a/frontend/vite.config.js b/frontend/vite.config.js index 0662188..71fe5dd 100644 --- a/frontend/vite.config.js +++ b/frontend/vite.config.js @@ -54,7 +54,7 @@ export default defineConfig({ cors: true, proxy: { '/api': { - target: 'http://192.168.1.38:8100', + target: 'http://192.168.1.58:8080', changeOrigin: true, secure: false, rewrite: path => path.replace(/^\/api/, '')