diff --git a/module-auth/src/main/java/com/ether/pms/auth/config/SecurityConfig.java b/module-auth/src/main/java/com/ether/pms/auth/config/SecurityConfig.java new file mode 100644 index 0000000..f213211 --- /dev/null +++ b/module-auth/src/main/java/com/ether/pms/auth/config/SecurityConfig.java @@ -0,0 +1,74 @@ +package com.ether.pms.auth.config; + +import com.ether.pms.auth.util.JwtTokenProvider; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +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.core.context.SecurityContextHolder; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; +import java.util.Collections; + +@Configuration +@EnableWebSecurity +@RequiredArgsConstructor +public class SecurityConfig { + + private final JwtTokenProvider jwtTokenProvider; + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + http + .csrf(csrf -> csrf.disable()) + .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + .authorizeHttpRequests(auth -> auth + .requestMatchers("/api/auth/login", "/api/auth/refresh").permitAll() + .requestMatchers("/api-docs/**", "/swagger-ui/**", "/swagger-ui.html").permitAll() + .requestMatchers("/actuator/**").permitAll() + .anyRequest().authenticated() + ) + .addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class); + + return http.build(); + } + + @Bean + public OncePerRequestFilter jwtAuthenticationFilter() { + return new OncePerRequestFilter() { + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws ServletException, IOException { + + String token = resolveToken(request); + + if (token != null && jwtTokenProvider.validateToken(token)) { + String username = jwtTokenProvider.getUsernameFromToken(token); + UsernamePasswordAuthenticationToken auth = + new UsernamePasswordAuthenticationToken(username, null, Collections.emptyList()); + SecurityContextHolder.getContext().setAuthentication(auth); + } + + filterChain.doFilter(request, response); + } + }; + } + + private String resolveToken(HttpServletRequest request) { + String bearerToken = request.getHeader("Authorization"); + if (bearerToken != null && bearerToken.startsWith("Bearer ")) { + return bearerToken.substring(7); + } + return null; + } +} diff --git a/module-auth/src/main/java/com/ether/pms/auth/controller/AuthController.java b/module-auth/src/main/java/com/ether/pms/auth/controller/AuthController.java new file mode 100644 index 0000000..95636f6 --- /dev/null +++ b/module-auth/src/main/java/com/ether/pms/auth/controller/AuthController.java @@ -0,0 +1,94 @@ +package com.ether.pms.auth.controller; + +import com.ether.pms.auth.service.LoginService; +import com.ether.pms.auth.util.JwtTokenProvider; +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.HashMap; +import java.util.Map; + +@RestController +@RequestMapping("/api/auth") +@RequiredArgsConstructor +public class AuthController { + + private final LoginService loginService; + private final JwtTokenProvider jwtTokenProvider; + + @PostMapping("/login") + public ResponseEntity> login( + @RequestParam String username, + @RequestParam String password, + HttpServletRequest request) { + + String ip = getClientIp(request); + Map result = loginService.login(username, password, ip); + return ResponseEntity.ok(result); + } + + @PostMapping("/logout") + public ResponseEntity logout(@RequestHeader(value = "Authorization", required = false) String token) { + return ResponseEntity.ok().build(); + } + + @GetMapping("/me") + public ResponseEntity> getCurrentUser( + @RequestHeader(value = "Authorization", required = false) String token) { + + if (token == null || !token.startsWith("Bearer ")) { + return ResponseEntity.status(401).build(); + } + + String jwt = token.substring(7); + if (!jwtTokenProvider.validateToken(jwt)) { + return ResponseEntity.status(401).build(); + } + + String username = jwtTokenProvider.getUsernameFromToken(jwt); + Map result = new HashMap<>(); + result.put("username", username); + + return ResponseEntity.ok(result); + } + + @PostMapping("/refresh") + public ResponseEntity> refreshToken( + @RequestHeader(value = "Authorization", required = false) String token) { + + if (token == null || !token.startsWith("Bearer ")) { + return ResponseEntity.status(401).build(); + } + + String jwt = token.substring(7); + if (!jwtTokenProvider.validateToken(jwt)) { + return ResponseEntity.status(401).build(); + } + + if (jwtTokenProvider.isTokenExpired(jwt)) { + return ResponseEntity.status(401).build(); + } + + String username = jwtTokenProvider.getUsernameFromToken(jwt); + String newToken = jwtTokenProvider.generateToken( + jwtTokenProvider.getUserIdFromToken(jwt), username); + + Map result = new HashMap<>(); + result.put("token", newToken); + + return ResponseEntity.ok(result); + } + + private String getClientIp(HttpServletRequest request) { + String ip = request.getHeader("X-Forwarded-For"); + if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("X-Real-IP"); + } + if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) { + ip = request.getRemoteAddr(); + } + return ip; + } +} diff --git a/module-auth/src/main/java/com/ether/pms/auth/controller/PermissionController.java b/module-auth/src/main/java/com/ether/pms/auth/controller/PermissionController.java new file mode 100644 index 0000000..36de4b0 --- /dev/null +++ b/module-auth/src/main/java/com/ether/pms/auth/controller/PermissionController.java @@ -0,0 +1,54 @@ +package com.ether.pms.auth.controller; + +import com.ether.pms.auth.entity.Permission; +import com.ether.pms.auth.service.PermissionService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.UUID; + +@RestController +@RequestMapping("/api/permissions") +@RequiredArgsConstructor +public class PermissionController { + + private final PermissionService permissionService; + + @GetMapping + public ResponseEntity> findAll() { + return ResponseEntity.ok(permissionService.findAll()); + } + + @GetMapping("/{id}") + public ResponseEntity findById(@PathVariable UUID id) { + return ResponseEntity.ok(permissionService.findById(id)); + } + + @GetMapping("/type/{type}") + public ResponseEntity> findByType(@PathVariable String type) { + return ResponseEntity.ok(permissionService.findByType(type)); + } + + @GetMapping("/menus") + public ResponseEntity> findMenus() { + return ResponseEntity.ok(permissionService.findMenuPermissions()); + } + + @PostMapping + public ResponseEntity create(@RequestBody Permission permission) { + return ResponseEntity.ok(permissionService.create(permission)); + } + + @PutMapping("/{id}") + public ResponseEntity update(@PathVariable UUID id, @RequestBody Permission permission) { + return ResponseEntity.ok(permissionService.update(id, permission)); + } + + @DeleteMapping("/{id}") + public ResponseEntity delete(@PathVariable UUID id) { + permissionService.delete(id); + return ResponseEntity.ok().build(); + } +} diff --git a/module-auth/src/main/java/com/ether/pms/auth/controller/RoleController.java b/module-auth/src/main/java/com/ether/pms/auth/controller/RoleController.java new file mode 100644 index 0000000..a56d7c6 --- /dev/null +++ b/module-auth/src/main/java/com/ether/pms/auth/controller/RoleController.java @@ -0,0 +1,57 @@ +package com.ether.pms.auth.controller; + +import com.ether.pms.auth.entity.Role; +import com.ether.pms.auth.service.RoleService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.UUID; + +@RestController +@RequestMapping("/api/roles") +@RequiredArgsConstructor +public class RoleController { + + private final RoleService roleService; + + @GetMapping + public ResponseEntity> findAll() { + return ResponseEntity.ok(roleService.findAll()); + } + + @GetMapping("/{id}") + public ResponseEntity findById(@PathVariable UUID id) { + return ResponseEntity.ok(roleService.findById(id)); + } + + @GetMapping("/project/{projectId}") + public ResponseEntity> findByProjectId(@PathVariable String projectId) { + return ResponseEntity.ok(roleService.findByProjectId(projectId)); + } + + @PostMapping + public ResponseEntity create(@RequestBody Role role) { + return ResponseEntity.ok(roleService.create(role)); + } + + @PutMapping("/{id}") + public ResponseEntity update(@PathVariable UUID id, @RequestBody Role role) { + return ResponseEntity.ok(roleService.update(id, role)); + } + + @DeleteMapping("/{id}") + public ResponseEntity delete(@PathVariable UUID id) { + roleService.delete(id); + return ResponseEntity.ok().build(); + } + + @PostMapping("/{id}/permissions") + public ResponseEntity assignPermissions( + @PathVariable UUID id, + @RequestBody List permissionIds) { + roleService.assignPermissions(id, permissionIds); + return ResponseEntity.ok().build(); + } +} diff --git a/module-auth/src/main/java/com/ether/pms/auth/controller/UserController.java b/module-auth/src/main/java/com/ether/pms/auth/controller/UserController.java new file mode 100644 index 0000000..a241b8b --- /dev/null +++ b/module-auth/src/main/java/com/ether/pms/auth/controller/UserController.java @@ -0,0 +1,61 @@ +package com.ether.pms.auth.controller; + +import com.ether.pms.auth.entity.User; +import com.ether.pms.auth.service.UserService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.UUID; + +@RestController +@RequestMapping("/api/users") +@RequiredArgsConstructor +public class UserController { + + private final UserService userService; + + @GetMapping + public ResponseEntity> findAll() { + return ResponseEntity.ok(userService.findAll()); + } + + @GetMapping("/{id}") + public ResponseEntity findById(@PathVariable UUID id) { + return ResponseEntity.ok(userService.findById(id)); + } + + @PostMapping + public ResponseEntity create(@RequestBody User user) { + return ResponseEntity.ok(userService.create(user)); + } + + @PutMapping("/{id}") + public ResponseEntity update(@PathVariable UUID id, @RequestBody User user) { + return ResponseEntity.ok(userService.update(id, user)); + } + + @DeleteMapping("/{id}") + public ResponseEntity delete(@PathVariable UUID id) { + userService.delete(id); + return ResponseEntity.ok().build(); + } + + @PutMapping("/{id}/password") + public ResponseEntity updatePassword( + @PathVariable UUID id, + @RequestParam String oldPassword, + @RequestParam String newPassword) { + userService.updatePassword(id, oldPassword, newPassword); + return ResponseEntity.ok().build(); + } + + @PostMapping("/{id}/roles") + public ResponseEntity assignRoles( + @PathVariable UUID id, + @RequestBody List roleIds) { + userService.assignRoles(id, roleIds); + return ResponseEntity.ok().build(); + } +} diff --git a/module-auth/src/main/java/com/ether/pms/auth/entity/Permission.java b/module-auth/src/main/java/com/ether/pms/auth/entity/Permission.java new file mode 100644 index 0000000..b43656b --- /dev/null +++ b/module-auth/src/main/java/com/ether/pms/auth/entity/Permission.java @@ -0,0 +1,63 @@ +package com.ether.pms.auth.entity; + +import jakarta.persistence.*; +import lombok.Data; +import java.time.LocalDateTime; +import java.util.List; +import java.util.UUID; + +@Entity +@Table(name = "auth_permission") +@Data +public class Permission { + + @Id + @GeneratedValue(strategy = GenerationType.UUID) + private UUID id; + + @Column(unique = true, nullable = false, length = 100) + private String code; + + @Column(nullable = false, length = 100) + private String name; + + @Column(length = 20) + private String type; + + @Column(length = 50) + private String resource; + + @Column(length = 50) + private String method; + + @Column(length = 200) + private String description; + + @Column(length = 50) + private String parentCode; + + private Integer sortOrder; + + @ManyToMany(fetch = FetchType.LAZY) + @JoinTable( + name = "auth_role_permission", + joinColumns = @JoinColumn(name = "permission_id"), + inverseJoinColumns = @JoinColumn(name = "role_id") + ) + private List roles; + + private LocalDateTime createdAt; + + private LocalDateTime updatedAt; + + @PrePersist + public void prePersist() { + this.createdAt = LocalDateTime.now(); + this.updatedAt = LocalDateTime.now(); + } + + @PreUpdate + public void preUpdate() { + this.updatedAt = LocalDateTime.now(); + } +} diff --git a/module-auth/src/main/java/com/ether/pms/auth/entity/Role.java b/module-auth/src/main/java/com/ether/pms/auth/entity/Role.java new file mode 100644 index 0000000..efb892f --- /dev/null +++ b/module-auth/src/main/java/com/ether/pms/auth/entity/Role.java @@ -0,0 +1,100 @@ +package com.ether.pms.auth.entity; + +import jakarta.persistence.*; +import lombok.Data; +import java.time.LocalDateTime; +import java.util.List; +import java.util.UUID; + +@Entity +@Table(name = "auth_role") +@Data +public class Role { + + @Id + @GeneratedValue(strategy = GenerationType.UUID) + private UUID id; + + @Column(unique = true, nullable = false, length = 50) + private String code; + + @Column(nullable = false, length = 50) + private String name; + + @Column(length = 200) + private String description; + + @Enumerated(EnumType.STRING) + @Column(length = 20) + private RoleType type; + + @Enumerated(EnumType.STRING) + @Column(length = 20) + private DataScope dataScope = DataScope.SELF; + + @Column(length = 50) + private String projectId; + + @Enumerated(EnumType.STRING) + @Column(length = 20) + private RoleStatus status = RoleStatus.ENABLED; + + @ManyToMany(fetch = FetchType.LAZY) + @JoinTable( + name = "auth_role_permission", + joinColumns = @JoinColumn(name = "role_id"), + inverseJoinColumns = @JoinColumn(name = "permission_id") + ) + private List permissions; + + private LocalDateTime createdAt; + + private LocalDateTime updatedAt; + + @PrePersist + public void prePersist() { + this.createdAt = LocalDateTime.now(); + this.updatedAt = LocalDateTime.now(); + } + + @PreUpdate + public void preUpdate() { + this.updatedAt = LocalDateTime.now(); + } + + public enum RoleType { + SYSTEM("系统级"), + PROJECT("项目级"), + DEPARTMENT("部门级"); + + private final String desc; + + RoleType(String desc) { + this.desc = desc; + } + } + + public enum DataScope { + ALL("全部"), + PROJECT("本项目"), + DEPARTMENT("本部门"), + SELF("本人"); + + private final String desc; + + DataScope(String desc) { + this.desc = desc; + } + } + + public enum RoleStatus { + ENABLED("启用"), + DISABLED("禁用"); + + private final String desc; + + RoleStatus(String desc) { + this.desc = desc; + } + } +} diff --git a/module-auth/src/main/java/com/ether/pms/auth/entity/User.java b/module-auth/src/main/java/com/ether/pms/auth/entity/User.java new file mode 100644 index 0000000..1158092 --- /dev/null +++ b/module-auth/src/main/java/com/ether/pms/auth/entity/User.java @@ -0,0 +1,85 @@ +package com.ether.pms.auth.entity; + +import jakarta.persistence.*; +import lombok.Data; +import java.time.LocalDateTime; +import java.util.List; +import java.util.UUID; + +@Entity +@Table(name = "auth_user") +@Data +public class User { + + @Id + @GeneratedValue(strategy = GenerationType.UUID) + private UUID id; + + @Column(unique = true, nullable = false, length = 50) + private String username; + + @Column(nullable = false) + private String password; + + private String salt; + + @Column(length = 50) + private String realName; + + @Column(length = 20) + private String phone; + + @Column(length = 100) + private String email; + + private String avatar; + + @Enumerated(EnumType.STRING) + @Column(length = 20) + private UserStatus status = UserStatus.ACTIVE; + + private LocalDateTime lastLoginTime; + + private String lastLoginIp; + + @ManyToMany(fetch = FetchType.LAZY) + @JoinTable( + name = "auth_user_role", + joinColumns = @JoinColumn(name = "user_id"), + inverseJoinColumns = @JoinColumn(name = "role_id") + ) + private List roles; + + private LocalDateTime createdAt; + + private LocalDateTime updatedAt; + + private UUID createdBy; + + @PrePersist + public void prePersist() { + this.createdAt = LocalDateTime.now(); + this.updatedAt = LocalDateTime.now(); + } + + @PreUpdate + public void preUpdate() { + this.updatedAt = LocalDateTime.now(); + } + + public enum UserStatus { + ACTIVE("正常"), + LOCKED("锁定"), + DISABLED("禁用"); + + private final String desc; + + UserStatus(String desc) { + this.desc = desc; + } + + public String getDesc() { + return desc; + } + } +} diff --git a/module-auth/src/main/java/com/ether/pms/auth/repository/PermissionRepository.java b/module-auth/src/main/java/com/ether/pms/auth/repository/PermissionRepository.java new file mode 100644 index 0000000..265a817 --- /dev/null +++ b/module-auth/src/main/java/com/ether/pms/auth/repository/PermissionRepository.java @@ -0,0 +1,17 @@ +package com.ether.pms.auth.repository; + +import com.ether.pms.auth.entity.Permission; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import java.util.List; +import java.util.UUID; + +@Repository +public interface PermissionRepository extends JpaRepository { + + List findByType(String type); + + List findByParentCode(String parentCode); + + boolean existsByCode(String code); +} diff --git a/module-auth/src/main/java/com/ether/pms/auth/repository/RoleRepository.java b/module-auth/src/main/java/com/ether/pms/auth/repository/RoleRepository.java new file mode 100644 index 0000000..3244b35 --- /dev/null +++ b/module-auth/src/main/java/com/ether/pms/auth/repository/RoleRepository.java @@ -0,0 +1,20 @@ +package com.ether.pms.auth.repository; + +import com.ether.pms.auth.entity.Role; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +@Repository +public interface RoleRepository extends JpaRepository { + + Optional findByCode(String code); + + boolean existsByCode(String code); + + List findByProjectId(String projectId); + + List findByType(Role.RoleType type); +} diff --git a/module-auth/src/main/java/com/ether/pms/auth/repository/UserRepository.java b/module-auth/src/main/java/com/ether/pms/auth/repository/UserRepository.java new file mode 100644 index 0000000..886377d --- /dev/null +++ b/module-auth/src/main/java/com/ether/pms/auth/repository/UserRepository.java @@ -0,0 +1,17 @@ +package com.ether.pms.auth.repository; + +import com.ether.pms.auth.entity.User; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import java.util.Optional; +import java.util.UUID; + +@Repository +public interface UserRepository extends JpaRepository { + + Optional findByUsername(String username); + + boolean existsByUsername(String username); + + boolean existsByPhone(String phone); +} diff --git a/module-auth/src/main/java/com/ether/pms/auth/service/LoginService.java b/module-auth/src/main/java/com/ether/pms/auth/service/LoginService.java new file mode 100644 index 0000000..e6df17e --- /dev/null +++ b/module-auth/src/main/java/com/ether/pms/auth/service/LoginService.java @@ -0,0 +1,62 @@ +package com.ether.pms.auth.service; + +import com.ether.pms.auth.entity.User; +import com.ether.pms.auth.repository.UserRepository; +import com.ether.pms.auth.util.JwtTokenProvider; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.HashMap; +import java.util.Map; + +@Service +@RequiredArgsConstructor +public class LoginService { + + private final UserRepository userRepository; + private final JwtTokenProvider jwtTokenProvider; + + public Map login(String username, String password, String ip) { + User user = userRepository.findByUsername(username) + .orElseThrow(() -> new RuntimeException("用户名或密码错误")); + + if (user.getStatus() == User.UserStatus.LOCKED) { + throw new RuntimeException("账号已被锁定"); + } + if (user.getStatus() == User.UserStatus.DISABLED) { + throw new RuntimeException("账号已被禁用"); + } + + String encryptedPassword = encryptPassword(password, user.getSalt()); + if (!encryptedPassword.equals(user.getPassword())) { + throw new RuntimeException("用户名或密码错误"); + } + + user.setLastLoginTime(java.time.LocalDateTime.now()); + user.setLastLoginIp(ip); + userRepository.save(user); + + Map claims = new HashMap<>(); + if (user.getRoles() != null) { + claims.put("roles", user.getRoles().stream() + .map(r -> r.getCode()) + .toList()); + } + + String token = jwtTokenProvider.generateToken(user.getId(), user.getUsername(), claims); + + Map result = new HashMap<>(); + result.put("token", token); + result.put("userId", user.getId()); + result.put("username", user.getUsername()); + result.put("realName", user.getRealName()); + + return result; + } + + private String encryptPassword(String password, String salt) { + String combined = password + salt; + return org.springframework.util.DigestUtils.md5DigestAsHex( + combined.getBytes(java.nio.charset.StandardCharsets.UTF_8)); + } +} diff --git a/module-auth/src/main/java/com/ether/pms/auth/service/PermissionService.java b/module-auth/src/main/java/com/ether/pms/auth/service/PermissionService.java new file mode 100644 index 0000000..0eae3ab --- /dev/null +++ b/module-auth/src/main/java/com/ether/pms/auth/service/PermissionService.java @@ -0,0 +1,80 @@ +package com.ether.pms.auth.service; + +import com.ether.pms.auth.entity.Permission; +import com.ether.pms.auth.repository.PermissionRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.UUID; + +@Service +@RequiredArgsConstructor +public class PermissionService { + + private final PermissionRepository permissionRepository; + + public List findAll() { + return permissionRepository.findAll(); + } + + public Permission findById(UUID id) { + return permissionRepository.findById(id) + .orElseThrow(() -> new RuntimeException("权限不存在")); + } + + public List findByType(String type) { + return permissionRepository.findByType(type); + } + + public List findByParentCode(String parentCode) { + return permissionRepository.findByParentCode(parentCode); + } + + public List findMenuPermissions() { + return permissionRepository.findByType("MENU"); + } + + @Transactional + public Permission create(Permission permission) { + if (permissionRepository.existsByCode(permission.getCode())) { + throw new RuntimeException("权限编码已存在"); + } + return permissionRepository.save(permission); + } + + @Transactional + public Permission update(UUID id, Permission permission) { + Permission existing = findById(id); + + if (permission.getName() != null) { + existing.setName(permission.getName()); + } + if (permission.getType() != null) { + existing.setType(permission.getType()); + } + if (permission.getResource() != null) { + existing.setResource(permission.getResource()); + } + if (permission.getMethod() != null) { + existing.setMethod(permission.getMethod()); + } + if (permission.getDescription() != null) { + existing.setDescription(permission.getDescription()); + } + if (permission.getParentCode() != null) { + existing.setParentCode(permission.getParentCode()); + } + if (permission.getSortOrder() != null) { + existing.setSortOrder(permission.getSortOrder()); + } + + return permissionRepository.save(existing); + } + + @Transactional + public void delete(UUID id) { + permissionRepository.deleteById(id); + } +} diff --git a/module-auth/src/main/java/com/ether/pms/auth/service/RoleService.java b/module-auth/src/main/java/com/ether/pms/auth/service/RoleService.java new file mode 100644 index 0000000..d46fcd7 --- /dev/null +++ b/module-auth/src/main/java/com/ether/pms/auth/service/RoleService.java @@ -0,0 +1,85 @@ +package com.ether.pms.auth.service; + +import com.ether.pms.auth.entity.Role; +import com.ether.pms.auth.entity.Permission; +import com.ether.pms.auth.repository.RoleRepository; +import com.ether.pms.auth.repository.PermissionRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.UUID; + +@Service +@RequiredArgsConstructor +public class RoleService { + + private final RoleRepository roleRepository; + private final PermissionRepository permissionRepository; + + public List findAll() { + return roleRepository.findAll(); + } + + public Role findById(UUID id) { + return roleRepository.findById(id) + .orElseThrow(() -> new RuntimeException("角色不存在")); + } + + public Role findByCode(String code) { + return roleRepository.findByCode(code) + .orElseThrow(() -> new RuntimeException("角色不存在")); + } + + public List findByProjectId(String projectId) { + return roleRepository.findByProjectId(projectId); + } + + @Transactional + public Role create(Role role) { + if (roleRepository.existsByCode(role.getCode())) { + throw new RuntimeException("角色编码已存在"); + } + return roleRepository.save(role); + } + + @Transactional + public Role update(UUID id, Role role) { + Role existing = findById(id); + + if (role.getName() != null) { + existing.setName(role.getName()); + } + if (role.getDescription() != null) { + existing.setDescription(role.getDescription()); + } + if (role.getType() != null) { + existing.setType(role.getType()); + } + if (role.getDataScope() != null) { + existing.setDataScope(role.getDataScope()); + } + if (role.getProjectId() != null) { + existing.setProjectId(role.getProjectId()); + } + if (role.getStatus() != null) { + existing.setStatus(role.getStatus()); + } + + return roleRepository.save(existing); + } + + @Transactional + public void assignPermissions(UUID roleId, List permissionIds) { + Role role = findById(roleId); + List permissions = permissionRepository.findAllById(permissionIds); + role.setPermissions(permissions); + roleRepository.save(role); + } + + @Transactional + public void delete(UUID id) { + roleRepository.deleteById(id); + } +} diff --git a/module-auth/src/main/java/com/ether/pms/auth/service/UserService.java b/module-auth/src/main/java/com/ether/pms/auth/service/UserService.java new file mode 100644 index 0000000..c168c76 --- /dev/null +++ b/module-auth/src/main/java/com/ether/pms/auth/service/UserService.java @@ -0,0 +1,135 @@ +package com.ether.pms.auth.service; + +import com.ether.pms.auth.entity.User; +import com.ether.pms.auth.entity.Role; +import com.ether.pms.auth.repository.UserRepository; +import com.ether.pms.auth.repository.RoleRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.DigestUtils; + +import java.nio.charset.StandardCharsets; +import java.time.LocalDateTime; +import java.util.List; +import java.util.UUID; + +@Service +@RequiredArgsConstructor +public class UserService { + + private final UserRepository userRepository; + private final RoleRepository roleRepository; + + public List findAll() { + return userRepository.findAll(); + } + + public User findById(UUID id) { + return userRepository.findById(id) + .orElseThrow(() -> new RuntimeException("用户不存在")); + } + + public User findByUsername(String username) { + return userRepository.findByUsername(username) + .orElseThrow(() -> new RuntimeException("用户不存在")); + } + + @Transactional + public User create(User user) { + if (userRepository.existsByUsername(user.getUsername())) { + throw new RuntimeException("用户名已存在"); + } + if (user.getPhone() != null && userRepository.existsByPhone(user.getPhone())) { + throw new RuntimeException("手机号已存在"); + } + + String salt = generateSalt(); + user.setSalt(salt); + user.setPassword(encryptPassword(user.getPassword(), salt)); + + return userRepository.save(user); + } + + @Transactional + public User update(UUID id, User user) { + User existing = findById(id); + + if (user.getRealName() != null) { + existing.setRealName(user.getRealName()); + } + if (user.getPhone() != null) { + if (!user.getPhone().equals(existing.getPhone()) && userRepository.existsByPhone(user.getPhone())) { + throw new RuntimeException("手机号已存在"); + } + existing.setPhone(user.getPhone()); + } + if (user.getEmail() != null) { + existing.setEmail(user.getEmail()); + } + if (user.getAvatar() != null) { + existing.setAvatar(user.getAvatar()); + } + if (user.getStatus() != null) { + existing.setStatus(user.getStatus()); + } + + return userRepository.save(existing); + } + + @Transactional + public void updatePassword(UUID id, String oldPassword, String newPassword) { + User user = findById(id); + String encryptedOld = encryptPassword(oldPassword, user.getSalt()); + + if (!encryptedOld.equals(user.getPassword())) { + throw new RuntimeException("原密码错误"); + } + + user.setSalt(generateSalt()); + user.setPassword(encryptPassword(newPassword, user.getSalt())); + userRepository.save(user); + } + + @Transactional + public void resetPassword(UUID id, String newPassword) { + User user = findById(id); + user.setSalt(generateSalt()); + user.setPassword(encryptPassword(newPassword, user.getSalt())); + userRepository.save(user); + } + + @Transactional + public void assignRoles(UUID userId, List roleIds) { + User user = findById(userId); + List roles = roleRepository.findAllById(roleIds); + user.setRoles(roles); + userRepository.save(user); + } + + @Transactional + public void delete(UUID id) { + userRepository.deleteById(id); + } + + public boolean verifyPassword(User user, String rawPassword) { + String encrypted = encryptPassword(rawPassword, user.getSalt()); + return encrypted.equals(user.getPassword()); + } + + public void updateLoginInfo(UUID userId, String ip) { + User user = findById(userId); + user.setLastLoginTime(LocalDateTime.now()); + user.setLastLoginIp(ip); + userRepository.save(user); + } + + private String generateSalt() { + return UUID.randomUUID().toString().replace("-", "").substring(0, 16); + } + + private String encryptPassword(String password, String salt) { + String combined = password + salt; + return DigestUtils.md5DigestAsHex(combined.getBytes(StandardCharsets.UTF_8)); + } +} diff --git a/module-auth/src/main/java/com/ether/pms/auth/util/JwtTokenProvider.java b/module-auth/src/main/java/com/ether/pms/auth/util/JwtTokenProvider.java new file mode 100644 index 0000000..070002d --- /dev/null +++ b/module-auth/src/main/java/com/ether/pms/auth/util/JwtTokenProvider.java @@ -0,0 +1,94 @@ +package com.ether.pms.auth.util; + +import io.jsonwebtoken.*; +import io.jsonwebtoken.security.Keys; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import javax.crypto.SecretKey; +import java.nio.charset.StandardCharsets; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +@Component +public class JwtTokenProvider { + + @Value("${jwt.secret:ether-pms-secret-key-must-be-at-least-256-bits}") + private String secret; + + @Value("${jwt.expiration:86400000}") + private long expiration; + + @Value("${jwt.issuer:ether-pms}") + private String issuer; + + private SecretKey getSigningKey() { + return Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8)); + } + + public String generateToken(UUID userId, String username, Map claims) { + Map allClaims = new HashMap<>(); + allClaims.put("userId", userId.toString()); + allClaims.put("username", username); + if (claims != null) { + allClaims.putAll(claims); + } + + Date now = new Date(); + Date expiryDate = new Date(now.getTime() + expiration); + + return Jwts.builder() + .claims(allClaims) + .subject(username) + .issuer(issuer) + .issuedAt(now) + .expiration(expiryDate) + .signWith(getSigningKey()) + .compact(); + } + + public String generateToken(UUID userId, String username) { + return generateToken(userId, username, null); + } + + public Claims getClaimsFromToken(String token) { + return Jwts.parser() + .verifyWith(getSigningKey()) + .build() + .parseSignedClaims(token) + .getPayload(); + } + + public UUID getUserIdFromToken(String token) { + Claims claims = getClaimsFromToken(token); + return UUID.fromString(claims.get("userId", String.class)); + } + + public String getUsernameFromToken(String token) { + Claims claims = getClaimsFromToken(token); + return claims.getSubject(); + } + + public boolean validateToken(String token) { + try { + Jwts.parser() + .verifyWith(getSigningKey()) + .build() + .parseSignedClaims(token); + return true; + } catch (JwtException | IllegalArgumentException e) { + return false; + } + } + + public boolean isTokenExpired(String token) { + try { + Claims claims = getClaimsFromToken(token); + return claims.getExpiration().before(new Date()); + } catch (Exception e) { + return true; + } + } +} diff --git a/module-mdm/src/main/java/com/ether/pms/mdm/controller/ProjectController.java b/module-mdm/src/main/java/com/ether/pms/mdm/controller/ProjectController.java new file mode 100644 index 0000000..4671b48 --- /dev/null +++ b/module-mdm/src/main/java/com/ether/pms/mdm/controller/ProjectController.java @@ -0,0 +1,49 @@ +package com.ether.pms.mdm.controller; + +import com.ether.pms.mdm.entity.Project; +import com.ether.pms.mdm.service.ProjectService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.UUID; + +@RestController +@RequestMapping("/api/projects") +@RequiredArgsConstructor +public class ProjectController { + + private final ProjectService projectService; + + @GetMapping + public ResponseEntity> findAll() { + return ResponseEntity.ok(projectService.findAll()); + } + + @GetMapping("/{id}") + public ResponseEntity findById(@PathVariable UUID id) { + return ResponseEntity.ok(projectService.findById(id)); + } + + @GetMapping("/code/{code}") + public ResponseEntity findByCode(@PathVariable String code) { + return ResponseEntity.ok(projectService.findByCode(code)); + } + + @PostMapping + public ResponseEntity create(@RequestBody Project project) { + return ResponseEntity.ok(projectService.create(project)); + } + + @PutMapping("/{id}") + public ResponseEntity update(@PathVariable UUID id, @RequestBody Project project) { + return ResponseEntity.ok(projectService.update(id, project)); + } + + @DeleteMapping("/{id}") + public ResponseEntity delete(@PathVariable UUID id) { + projectService.delete(id); + return ResponseEntity.ok().build(); + } +} diff --git a/module-mdm/src/main/java/com/ether/pms/mdm/controller/SpaceNodeController.java b/module-mdm/src/main/java/com/ether/pms/mdm/controller/SpaceNodeController.java new file mode 100644 index 0000000..f548816 --- /dev/null +++ b/module-mdm/src/main/java/com/ether/pms/mdm/controller/SpaceNodeController.java @@ -0,0 +1,61 @@ +package com.ether.pms.mdm.controller; + +import com.ether.pms.mdm.entity.SpaceNode; +import com.ether.pms.mdm.service.SpaceNodeService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.UUID; + +@RestController +@RequestMapping("/api/space-nodes") +@RequiredArgsConstructor +public class SpaceNodeController { + + private final SpaceNodeService spaceNodeService; + + @GetMapping + public ResponseEntity> findAll() { + return ResponseEntity.ok(spaceNodeService.findAll()); + } + + @GetMapping("/{id}") + public ResponseEntity findById(@PathVariable UUID id) { + return ResponseEntity.ok(spaceNodeService.findById(id)); + } + + @GetMapping("/project/{projectCode}") + public ResponseEntity> findByProject(@PathVariable String projectCode) { + return ResponseEntity.ok(spaceNodeService.findByProject(projectCode)); + } + + @GetMapping("/project/{projectCode}/type/{nodeType}") + public ResponseEntity> findByProjectAndType( + @PathVariable String projectCode, + @PathVariable String nodeType) { + return ResponseEntity.ok(spaceNodeService.findByProjectAndType(projectCode, nodeType)); + } + + @GetMapping("/parent/{parentCode}") + public ResponseEntity> findByParent(@PathVariable String parentCode) { + return ResponseEntity.ok(spaceNodeService.findByParent(parentCode)); + } + + @PostMapping + public ResponseEntity create(@RequestBody SpaceNode spaceNode) { + return ResponseEntity.ok(spaceNodeService.create(spaceNode)); + } + + @PutMapping("/{id}") + public ResponseEntity update(@PathVariable UUID id, @RequestBody SpaceNode spaceNode) { + return ResponseEntity.ok(spaceNodeService.update(id, spaceNode)); + } + + @DeleteMapping("/{id}") + public ResponseEntity delete(@PathVariable UUID id) { + spaceNodeService.delete(id); + return ResponseEntity.ok().build(); + } +} diff --git a/module-mdm/src/main/java/com/ether/pms/mdm/entity/Project.java b/module-mdm/src/main/java/com/ether/pms/mdm/entity/Project.java new file mode 100644 index 0000000..2375dfc --- /dev/null +++ b/module-mdm/src/main/java/com/ether/pms/mdm/entity/Project.java @@ -0,0 +1,82 @@ +package com.ether.pms.mdm.entity; + +import jakarta.persistence.*; +import lombok.Data; +import java.time.LocalDateTime; +import java.util.UUID; + +@Entity +@Table(name = "mdm_project") +@Data +public class Project { + + @Id + @GeneratedValue(strategy = GenerationType.UUID) + private UUID id; + + @Column(unique = true, nullable = false, length = 50) + private String code; + + @Column(nullable = false, length = 100) + private String name; + + @Column(length = 500) + private String description; + + @Column(length = 100) + private String address; + + @Column(length = 20) + private String projectType; + + @Column(length = 50) + private String province; + + @Column(length = 50) + private String city; + + @Column(length = 50) + private String district; + + private Double longitude; + + private Double latitude; + + @Column(length = 20) + private String status; + + private Integer buildingCount; + + private Integer unitCount; + + private Integer roomCount; + + private Integer floorCount; + + @Column(length = 200) + private String logo; + + @Column(length = 200) + private String contact; + + @Column(length = 20) + private String contactPhone; + + private LocalDateTime createdAt; + + private LocalDateTime updatedAt; + + @PrePersist + public void prePersist() { + this.createdAt = LocalDateTime.now(); + this.updatedAt = LocalDateTime.now(); + if (this.status == null) { + this.status = "ACTIVE"; + } + } + + @PreUpdate + public void preUpdate() { + this.updatedAt = LocalDateTime.now(); + } +} diff --git a/module-mdm/src/main/java/com/ether/pms/mdm/entity/SpaceNode.java b/module-mdm/src/main/java/com/ether/pms/mdm/entity/SpaceNode.java new file mode 100644 index 0000000..cf70fc6 --- /dev/null +++ b/module-mdm/src/main/java/com/ether/pms/mdm/entity/SpaceNode.java @@ -0,0 +1,88 @@ +package com.ether.pms.mdm.entity; + +import jakarta.persistence.*; +import lombok.Data; +import java.time.LocalDateTime; +import java.util.UUID; + +@Entity +@Table(name = "mdm_space_node") +@Data +public class SpaceNode { + + @Id + @GeneratedValue(strategy = GenerationType.UUID) + private UUID id; + + @Column(nullable = false, length = 50) + private String code; + + @Column(nullable = false, length = 100) + private String name; + + @Column(nullable = false, length = 50) + private String nodeType; + + @Column(length = 50) + private String parentCode; + + @Column(nullable = false) + private String projectCode; + + private Integer sortOrder; + + @Column(length = 50) + private String building; + + @Column(length = 50) + private String unit; + + @Column(length = 50) + private String floor; + + @Column(length = 50) + private String roomNumber; + + private Integer area; + + @Column(length = 20) + private String status; + + private LocalDateTime createdAt; + + private LocalDateTime updatedAt; + + @PrePersist + public void prePersist() { + this.createdAt = LocalDateTime.now(); + this.updatedAt = LocalDateTime.now(); + if (this.status == null) { + this.status = "ACTIVE"; + } + if (this.sortOrder == null) { + this.sortOrder = 0; + } + } + + @PreUpdate + public void preUpdate() { + this.updatedAt = LocalDateTime.now(); + } + + public enum NodeType { + PROJECT("项目"), + PHASE("期"), + BUILDING("楼栋"), + UNIT("单元"), + FLOOR("楼层"), + ROOM("房间"), + PARKING("车位"), + STORE("商铺"); + + private final String desc; + + NodeType(String desc) { + this.desc = desc; + } + } +} diff --git a/module-mdm/src/main/java/com/ether/pms/mdm/repository/ProjectRepository.java b/module-mdm/src/main/java/com/ether/pms/mdm/repository/ProjectRepository.java new file mode 100644 index 0000000..836a344 --- /dev/null +++ b/module-mdm/src/main/java/com/ether/pms/mdm/repository/ProjectRepository.java @@ -0,0 +1,15 @@ +package com.ether.pms.mdm.repository; + +import com.ether.pms.mdm.entity.Project; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import java.util.Optional; +import java.util.UUID; + +@Repository +public interface ProjectRepository extends JpaRepository { + + Optional findByCode(String code); + + boolean existsByCode(String code); +} diff --git a/module-mdm/src/main/java/com/ether/pms/mdm/repository/SpaceNodeRepository.java b/module-mdm/src/main/java/com/ether/pms/mdm/repository/SpaceNodeRepository.java new file mode 100644 index 0000000..ec80642 --- /dev/null +++ b/module-mdm/src/main/java/com/ether/pms/mdm/repository/SpaceNodeRepository.java @@ -0,0 +1,20 @@ +package com.ether.pms.mdm.repository; + +import com.ether.pms.mdm.entity.SpaceNode; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.UUID; + +@Repository +public interface SpaceNodeRepository extends JpaRepository { + + List findByProjectCode(String projectCode); + + List findByProjectCodeAndNodeType(String projectCode, String nodeType); + + List findByParentCode(String parentCode); + + boolean existsByCode(String code); +} diff --git a/module-mdm/src/main/java/com/ether/pms/mdm/service/ProjectService.java b/module-mdm/src/main/java/com/ether/pms/mdm/service/ProjectService.java new file mode 100644 index 0000000..6c7aa88 --- /dev/null +++ b/module-mdm/src/main/java/com/ether/pms/mdm/service/ProjectService.java @@ -0,0 +1,103 @@ +package com.ether.pms.mdm.service; + +import com.ether.pms.mdm.entity.Project; +import com.ether.pms.mdm.repository.ProjectRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.UUID; + +@Service +@RequiredArgsConstructor +public class ProjectService { + + private final ProjectRepository projectRepository; + + public List findAll() { + return projectRepository.findAll(); + } + + public Project findById(UUID id) { + return projectRepository.findById(id) + .orElseThrow(() -> new RuntimeException("项目不存在")); + } + + public Project findByCode(String code) { + return projectRepository.findByCode(code) + .orElseThrow(() -> new RuntimeException("项目不存在")); + } + + @Transactional + public Project create(Project project) { + if (projectRepository.existsByCode(project.getCode())) { + throw new RuntimeException("项目编码已存在"); + } + return projectRepository.save(project); + } + + @Transactional + public Project update(UUID id, Project project) { + Project existing = findById(id); + + if (project.getName() != null) { + existing.setName(project.getName()); + } + if (project.getDescription() != null) { + existing.setDescription(project.getDescription()); + } + if (project.getAddress() != null) { + existing.setAddress(project.getAddress()); + } + if (project.getProjectType() != null) { + existing.setProjectType(project.getProjectType()); + } + if (project.getProvince() != null) { + existing.setProvince(project.getProvince()); + } + if (project.getCity() != null) { + existing.setCity(project.getCity()); + } + if (project.getDistrict() != null) { + existing.setDistrict(project.getDistrict()); + } + if (project.getLongitude() != null) { + existing.setLongitude(project.getLongitude()); + } + if (project.getLatitude() != null) { + existing.setLatitude(project.getLatitude()); + } + if (project.getStatus() != null) { + existing.setStatus(project.getStatus()); + } + if (project.getBuildingCount() != null) { + existing.setBuildingCount(project.getBuildingCount()); + } + if (project.getUnitCount() != null) { + existing.setUnitCount(project.getUnitCount()); + } + if (project.getRoomCount() != null) { + existing.setRoomCount(project.getRoomCount()); + } + if (project.getFloorCount() != null) { + existing.setFloorCount(project.getFloorCount()); + } + if (project.getLogo() != null) { + existing.setLogo(project.getLogo()); + } + if (project.getContact() != null) { + existing.setContact(project.getContact()); + } + if (project.getContactPhone() != null) { + existing.setContactPhone(project.getContactPhone()); + } + + return projectRepository.save(existing); + } + + @Transactional + public void delete(UUID id) { + projectRepository.deleteById(id); + } +} diff --git a/module-mdm/src/main/java/com/ether/pms/mdm/service/SpaceNodeService.java b/module-mdm/src/main/java/com/ether/pms/mdm/service/SpaceNodeService.java new file mode 100644 index 0000000..eaa846c --- /dev/null +++ b/module-mdm/src/main/java/com/ether/pms/mdm/service/SpaceNodeService.java @@ -0,0 +1,89 @@ +package com.ether.pms.mdm.service; + +import com.ether.pms.mdm.entity.SpaceNode; +import com.ether.pms.mdm.repository.SpaceNodeRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.UUID; + +@Service +@RequiredArgsConstructor +public class SpaceNodeService { + + private final SpaceNodeRepository spaceNodeRepository; + + public List findAll() { + return spaceNodeRepository.findAll(); + } + + public SpaceNode findById(UUID id) { + return spaceNodeRepository.findById(id) + .orElseThrow(() -> new RuntimeException("空间节点不存在")); + } + + public List findByProject(String projectCode) { + return spaceNodeRepository.findByProjectCode(projectCode); + } + + public List findByProjectAndType(String projectCode, String nodeType) { + return spaceNodeRepository.findByProjectCodeAndNodeType(projectCode, nodeType); + } + + public List findByParent(String parentCode) { + return spaceNodeRepository.findByParentCode(parentCode); + } + + @Transactional + public SpaceNode create(SpaceNode spaceNode) { + if (spaceNodeRepository.existsByCode(spaceNode.getCode())) { + throw new RuntimeException("空间节点编码已存在"); + } + return spaceNodeRepository.save(spaceNode); + } + + @Transactional + public SpaceNode update(UUID id, SpaceNode spaceNode) { + SpaceNode existing = findById(id); + + if (spaceNode.getName() != null) { + existing.setName(spaceNode.getName()); + } + if (spaceNode.getNodeType() != null) { + existing.setNodeType(spaceNode.getNodeType()); + } + if (spaceNode.getParentCode() != null) { + existing.setParentCode(spaceNode.getParentCode()); + } + if (spaceNode.getSortOrder() != null) { + existing.setSortOrder(spaceNode.getSortOrder()); + } + if (spaceNode.getBuilding() != null) { + existing.setBuilding(spaceNode.getBuilding()); + } + if (spaceNode.getUnit() != null) { + existing.setUnit(spaceNode.getUnit()); + } + if (spaceNode.getFloor() != null) { + existing.setFloor(spaceNode.getFloor()); + } + if (spaceNode.getRoomNumber() != null) { + existing.setRoomNumber(spaceNode.getRoomNumber()); + } + if (spaceNode.getArea() != null) { + existing.setArea(spaceNode.getArea()); + } + if (spaceNode.getStatus() != null) { + existing.setStatus(spaceNode.getStatus()); + } + + return spaceNodeRepository.save(existing); + } + + @Transactional + public void delete(UUID id) { + spaceNodeRepository.deleteById(id); + } +} diff --git a/sql/init.sql b/sql/init.sql new file mode 100644 index 0000000..64e5cbb --- /dev/null +++ b/sql/init.sql @@ -0,0 +1,173 @@ +-- Ether PMS Database Initialization Script +-- Database: ether_pms + +-- ============================================ +-- Auth Module Tables +-- ============================================ + +-- User Table +CREATE TABLE IF NOT EXISTS auth_user ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + username VARCHAR(50) UNIQUE NOT NULL, + password VARCHAR(64) NOT NULL, + salt VARCHAR(32), + real_name VARCHAR(50), + phone VARCHAR(20), + email VARCHAR(100), + avatar VARCHAR(200), + status VARCHAR(20) DEFAULT 'ACTIVE', + last_login_time TIMESTAMP, + last_login_ip VARCHAR(50), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + created_by UUID +); + +CREATE INDEX idx_auth_user_username ON auth_user(username); +CREATE INDEX idx_auth_user_phone ON auth_user(phone); +CREATE INDEX idx_auth_user_status ON auth_user(status); + +-- Role Table +CREATE TABLE IF NOT EXISTS auth_role ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + code VARCHAR(50) UNIQUE NOT NULL, + name VARCHAR(50) NOT NULL, + description VARCHAR(200), + type VARCHAR(20), + data_scope VARCHAR(20) DEFAULT 'SELF', + project_id VARCHAR(50), + status VARCHAR(20) DEFAULT 'ENABLED', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +CREATE INDEX idx_auth_role_code ON auth_role(code); +CREATE INDEX idx_auth_role_project ON auth_role(project_id); + +-- Permission Table +CREATE TABLE IF NOT EXISTS auth_permission ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + code VARCHAR(100) UNIQUE NOT NULL, + name VARCHAR(100) NOT NULL, + type VARCHAR(20), + resource VARCHAR(50), + method VARCHAR(50), + description VARCHAR(200), + parent_code VARCHAR(50), + sort_order INTEGER DEFAULT 0, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +CREATE INDEX idx_auth_permission_code ON auth_permission(code); +CREATE INDEX idx_auth_permission_type ON auth_permission(type); +CREATE INDEX idx_auth_permission_parent ON auth_permission(parent_code); + +-- User-Role Relation Table +CREATE TABLE IF NOT EXISTS auth_user_role ( + user_id UUID REFERENCES auth_user(id) ON DELETE CASCADE, + role_id UUID REFERENCES auth_role(id) ON DELETE CASCADE, + PRIMARY KEY (user_id, role_id) +); + +-- Role-Permission Relation Table +CREATE TABLE IF NOT EXISTS auth_role_permission ( + role_id UUID REFERENCES auth_role(id) ON DELETE CASCADE, + permission_id UUID REFERENCES auth_permission(id) ON DELETE CASCADE, + PRIMARY KEY (role_id, permission_id) +); + +-- ============================================ +-- MDM Module Tables +-- ============================================ + +-- Project Table +CREATE TABLE IF NOT EXISTS mdm_project ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + code VARCHAR(50) UNIQUE NOT NULL, + name VARCHAR(100) NOT NULL, + description VARCHAR(500), + address VARCHAR(200), + project_type VARCHAR(20), + province VARCHAR(50), + city VARCHAR(50), + district VARCHAR(50), + longitude DOUBLE PRECISION, + latitude DOUBLE PRECISION, + status VARCHAR(20) DEFAULT 'ACTIVE', + building_count INTEGER, + unit_count INTEGER, + room_count INTEGER, + floor_count INTEGER, + logo VARCHAR(200), + contact VARCHAR(200), + contact_phone VARCHAR(20), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +CREATE INDEX idx_mdm_project_code ON mdm_project(code); +CREATE INDEX idx_mdm_project_status ON mdm_project(status); + +-- Space Node Table +CREATE TABLE IF NOT EXISTS mdm_space_node ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + code VARCHAR(50) NOT NULL, + name VARCHAR(100) NOT NULL, + node_type VARCHAR(50) NOT NULL, + parent_code VARCHAR(50), + project_code VARCHAR(50) NOT NULL, + sort_order INTEGER DEFAULT 0, + building VARCHAR(50), + unit VARCHAR(50), + floor VARCHAR(50), + room_number VARCHAR(50), + area INTEGER, + status VARCHAR(20) DEFAULT 'ACTIVE', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + UNIQUE(code, project_code) +); + +CREATE INDEX idx_mdm_space_node_project ON mdm_space_node(project_code); +CREATE INDEX idx_mdm_space_node_type ON mdm_space_node(node_type); +CREATE INDEX idx_mdm_space_node_parent ON mdm_space_node(parent_code); + +-- ============================================ +-- Initial Data +-- ============================================ + +-- Insert default admin user (password: admin123, salt: admin) +-- Password = MD5("admin123" + "admin") = "fcea920f7412b5da7be0cf42b8c93759" +INSERT INTO auth_user (username, password, salt, real_name, status) +VALUES ('admin', 'fcea920f7412b5da7be0cf42b8c93759', 'admin', '系统管理员', 'ACTIVE'); + +-- Insert default roles +INSERT INTO auth_role (code, name, description, type, data_scope, status) +VALUES + ('SYSTEM_ADMIN', '系统管理员', '系统超级管理员', 'SYSTEM', 'ALL', 'ENABLED'), + ('PROJECT_ADMIN', '项目管理员', '项目管理员', 'PROJECT', 'PROJECT', 'ENABLED'), + ('EMPLOYEE', '普通员工', '普通员工', 'DEPARTMENT', 'SELF', 'ENABLED'); + +-- Insert default permissions +INSERT INTO auth_permission (code, name, type, resource, method, sort_order) +VALUES + ('dashboard', '仪表盘', 'MENU', '/dashboard', 'GET', 1), + ('user:list', '用户列表', 'BUTTON', '/api/users', 'GET', 10), + ('user:create', '创建用户', 'BUTTON', '/api/users', 'POST', 11), + ('user:update', '更新用户', 'BUTTON', '/api/users', 'PUT', 12), + ('user:delete', '删除用户', 'BUTTON', '/api/users', 'DELETE', 13), + ('role:list', '角色列表', 'BUTTON', '/api/roles', 'GET', 20), + ('role:create', '创建角色', 'BUTTON', '/api/roles', 'POST', 21), + ('role:update', '更新角色', 'BUTTON', '/api/roles', 'PUT', 22), + ('role:delete', '删除角色', 'BUTTON', '/api/roles', 'DELETE', 23), + ('project:list', '项目列表', 'BUTTON', '/api/projects', 'GET', 30), + ('project:create', '创建项目', 'BUTTON', '/api/projects', 'POST', 31), + ('project:update', '更新项目', 'BUTTON', '/api/projects', 'PUT', 32), + ('project:delete', '删除项目', 'BUTTON', '/api/projects', 'DELETE', 33); + +-- Assign all permissions to SYSTEM_ADMIN role +INSERT INTO auth_role_permission (role_id, permission_id) +SELECT r.id, p.id +FROM auth_role r, auth_permission p +WHERE r.code = 'SYSTEM_ADMIN';