diff --git a/pom.xml b/pom.xml
index ab68f9a..68fdafb 100644
--- a/pom.xml
+++ b/pom.xml
@@ -78,6 +78,16 @@
org.springframework.boot
spring-boot-starter-security
+
+ io.jsonwebtoken
+ jjwt
+ 0.9.1
+
+
+ org.springframework.security
+ spring-security-web
+
+
diff --git a/src/main/java/com/waterquality/projectmanagement/config/JwtAuthenticationFilter.java b/src/main/java/com/waterquality/projectmanagement/config/JwtAuthenticationFilter.java
new file mode 100644
index 0000000..69ab391
--- /dev/null
+++ b/src/main/java/com/waterquality/projectmanagement/config/JwtAuthenticationFilter.java
@@ -0,0 +1,49 @@
+package com.waterquality.projectmanagement.config;
+
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.RequiredArgsConstructor;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+import java.io.IOException;
+
+@RequiredArgsConstructor
+public class JwtAuthenticationFilter extends OncePerRequestFilter {
+
+ private final JwtTokenProvider jwtTokenProvider;
+
+ @Override
+ protected void doFilterInternal(
+ HttpServletRequest request,
+ HttpServletResponse response,
+ FilterChain filterChain
+ ) throws ServletException, IOException {
+ // 1. 从请求头中获取 JWT Token
+ String token = resolveToken(request);
+
+ // 2. 验证 Token 是否有效
+ if (token != null && jwtTokenProvider.validateToken(token)) {
+ // 3. 如果 Token 有效,获取认证信息
+ Authentication authentication = jwtTokenProvider.getAuthentication(token);
+
+ // 4. 将认证信息设置到 SecurityContext 中
+ SecurityContextHolder.getContext().setAuthentication(authentication);
+ }
+
+ // 5. 继续执行过滤器链
+ filterChain.doFilter(request, response);
+ }
+
+ // 从请求头中解析 JWT Token
+ private String resolveToken(HttpServletRequest request) {
+ String bearerToken = request.getHeader("Authorization");
+ if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
+ return bearerToken.substring(7);
+ }
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/waterquality/projectmanagement/config/JwtConfigurer.java b/src/main/java/com/waterquality/projectmanagement/config/JwtConfigurer.java
new file mode 100644
index 0000000..fd2157e
--- /dev/null
+++ b/src/main/java/com/waterquality/projectmanagement/config/JwtConfigurer.java
@@ -0,0 +1,21 @@
+package com.waterquality.projectmanagement.config;
+
+import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.web.DefaultSecurityFilterChain;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+
+public class JwtConfigurer extends SecurityConfigurerAdapter {
+
+ private final JwtTokenProvider jwtTokenProvider;
+
+ public JwtConfigurer(JwtTokenProvider jwtTokenProvider) {
+ this.jwtTokenProvider = jwtTokenProvider;
+ }
+
+ @Override
+ public void configure(HttpSecurity http) {
+ JwtTokenFilter customFilter = new JwtTokenFilter(jwtTokenProvider);
+ http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/waterquality/projectmanagement/config/JwtTokenFilter.java b/src/main/java/com/waterquality/projectmanagement/config/JwtTokenFilter.java
new file mode 100644
index 0000000..9ed0e34
--- /dev/null
+++ b/src/main/java/com/waterquality/projectmanagement/config/JwtTokenFilter.java
@@ -0,0 +1,40 @@
+package com.waterquality.projectmanagement.config;
+
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+import java.io.IOException;
+
+public class JwtTokenFilter extends OncePerRequestFilter {
+
+ private final JwtTokenProvider jwtTokenProvider;
+
+ public JwtTokenFilter(JwtTokenProvider jwtTokenProvider) {
+ this.jwtTokenProvider = jwtTokenProvider;
+ }
+
+ @Override
+ protected void doFilterInternal(HttpServletRequest request,
+ HttpServletResponse response,
+ FilterChain filterChain) throws ServletException, IOException {
+ String token = resolveToken(request);
+ if (token != null && jwtTokenProvider.validateToken(token)) {
+ Authentication auth = jwtTokenProvider.getAuthentication(token);
+ SecurityContextHolder.getContext().setAuthentication(auth);
+ }
+ filterChain.doFilter(request, response);
+ }
+
+ private String resolveToken(HttpServletRequest req) {
+ String bearerToken = req.getHeader("Authorization");
+ if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
+ return bearerToken.substring(7);
+ }
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/waterquality/projectmanagement/config/JwtTokenProvider.java b/src/main/java/com/waterquality/projectmanagement/config/JwtTokenProvider.java
new file mode 100644
index 0000000..a0cea7a
--- /dev/null
+++ b/src/main/java/com/waterquality/projectmanagement/config/JwtTokenProvider.java
@@ -0,0 +1,74 @@
+package com.waterquality.projectmanagement.config;
+
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.SignatureAlgorithm;
+import org.springframework.beans.factory.annotation.Value; // 确保导入这个
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.stereotype.Component;
+import com.waterquality.projectmanagement.repository.EmployeeRepository; // 确保正确导入你的 EmployeeRepository
+
+import java.util.Date;
+import java.util.stream.Collectors;
+
+@Component
+public class JwtTokenProvider {
+
+ @Value("${jwt.secret}")
+ private String secret;
+
+ @Value("${jwt.expiration}")
+ private long validityInMilliseconds;
+
+ @Autowired
+ private EmployeeRepository employeeRepository; // 添加 EmployeeRepository 的依赖注入
+
+ public String createToken(UserDetails userDetails) {
+ Claims claims = Jwts.claims().setSubject(userDetails.getUsername());
+ claims.put("roles", userDetails.getAuthorities().stream()
+ .map(GrantedAuthority::getAuthority)
+ .collect(Collectors.toList()));
+
+ Date now = new Date();
+ Date validity = new Date(now.getTime() + validityInMilliseconds);
+
+ return Jwts.builder()
+ .setClaims(claims)
+ .setIssuedAt(now)
+ .setExpiration(validity)
+ .signWith(SignatureAlgorithm.HS256, secret)
+ .compact();
+ }
+
+ public Authentication getAuthentication(String token) {
+ UserDetails userDetails = (UserDetails) employeeRepository.findByUsername(getUsername(token))
+ .orElseThrow(() -> new UsernameNotFoundException("用户不存在"));
+ return new UsernamePasswordAuthenticationToken(userDetails, "", userDetails.getAuthorities());
+ }
+
+ private String getUsername(String token) {
+ return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody().getSubject();
+ }
+
+ public boolean validateToken(String token) {
+ try {
+ Jwts.parser().setSigningKey(secret).parseClaimsJws(token);
+ return true; // 验证成功
+ } catch (io.jsonwebtoken.ExpiredJwtException e) {
+ // 处理过期的令牌
+ return false;
+ } catch (io.jsonwebtoken.SignatureException e) {
+ // 处理签名异常
+ return false;
+ } catch (Exception e) {
+ // 处理其他异常
+ return false;
+ }
+ }
+ // 其他验证方法...
+}
\ No newline at end of file
diff --git a/src/main/java/com/waterquality/projectmanagement/config/SecurityConfig.java b/src/main/java/com/waterquality/projectmanagement/config/SecurityConfig.java
new file mode 100644
index 0000000..41f8121
--- /dev/null
+++ b/src/main/java/com/waterquality/projectmanagement/config/SecurityConfig.java
@@ -0,0 +1,70 @@
+package com.waterquality.projectmanagement.config;
+
+import com.waterquality.projectmanagement.repository.EmployeeRepository;
+import jakarta.servlet.Filter;
+import lombok.RequiredArgsConstructor;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.AuthenticationProvider;
+import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
+import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
+import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
+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.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+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;
+
+@Configuration
+@EnableWebSecurity
+@EnableMethodSecurity(prePostEnabled = true)
+@RequiredArgsConstructor
+public class SecurityConfig {
+
+ private final EmployeeRepository employeeRepository;
+ private final JwtTokenProvider jwtTokenProvider;
+
+ @Bean
+ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
+ http
+ .csrf(csrf -> csrf.disable())
+ .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
+ .authorizeHttpRequests(authz -> authz
+ .requestMatchers("/api/auth/**").permitAll()
+ .anyRequest().authenticated())
+ .authenticationProvider(authenticationProvider())
+ .addFilterBefore((Filter) new JwtAuthenticationFilter(jwtTokenProvider), UsernamePasswordAuthenticationFilter.class);
+
+ return http.build();
+ }
+
+ @Bean
+ public UserDetailsService userDetailsService() {
+ return username -> (UserDetails) employeeRepository.findByUsername(username)
+ .orElseThrow(() -> new UsernameNotFoundException("用户不存在"));
+ }
+
+ @Bean
+ public PasswordEncoder passwordEncoder() {
+ return new BCryptPasswordEncoder();
+ }
+
+ @Bean
+ public AuthenticationProvider authenticationProvider() {
+ DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
+ authProvider.setUserDetailsService(userDetailsService());
+ authProvider.setPasswordEncoder(passwordEncoder());
+ return authProvider;
+ }
+
+ @Bean
+ public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
+ return config.getAuthenticationManager();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/waterquality/projectmanagement/controller/AuthController.java b/src/main/java/com/waterquality/projectmanagement/controller/AuthController.java
new file mode 100644
index 0000000..10f0306
--- /dev/null
+++ b/src/main/java/com/waterquality/projectmanagement/controller/AuthController.java
@@ -0,0 +1,70 @@
+package com.waterquality.projectmanagement.controller;
+
+// 明文密码,请注意
+
+import com.waterquality.projectmanagement.config.JwtTokenProvider;
+import com.waterquality.projectmanagement.dto.login.AuthResponse;
+import com.waterquality.projectmanagement.dto.login.LoginDTO;
+import com.waterquality.projectmanagement.entity.employee.Employee;
+import com.waterquality.projectmanagement.repository.EmployeeRepository;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.validation.Valid;
+
+@RestController
+@RequestMapping("/api/auth")
+@RequiredArgsConstructor
+@Slf4j
+public class AuthController {
+
+ private final AuthenticationManager authenticationManager;
+ private final JwtTokenProvider jwtTokenProvider;
+ private final EmployeeRepository employeeRepository;
+
+ @PostMapping("/login")
+ public ResponseEntity> login(@Valid @RequestBody LoginDTO dto) {
+ try {
+ // 查找用户
+ Employee employee = (Employee) employeeRepository.findByUsername(dto.getUsername())
+ .orElseThrow(() -> new BadCredentialsException("用户不存在"));
+
+ // 检查密码是否匹配
+ if (!employee.getPassword().equals(dto.getPassword())) {
+ log.error("登录失败 - 无效凭证: {},原因: Bad credentials", dto.getUsername());
+ return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("用户名或密码错误");
+ }
+
+ // 检查账号状态(可选)
+ if (!employee.isEnabled()) {
+ log.warn("尝试登录的账号已被禁用: {}", dto.getUsername());
+ return ResponseEntity.status(HttpStatus.FORBIDDEN).body("账号已被禁用");
+ }
+
+ // 生成token
+ String token = jwtTokenProvider.createToken(employee);
+
+ // 返回认证响应
+ return ResponseEntity.ok(new AuthResponse(
+ token,
+ employee.getEmployeeId(),
+ employee.getName(),
+ employee.getPosition().name()
+ ));
+
+ } catch (Exception e) {
+ log.error("登录过程中发生未知错误,用户: {},异常信息: {}", dto.getUsername(), e.getMessage(), e);
+ return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("系统错误");
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/waterquality/projectmanagement/controller/c.java b/src/main/java/com/waterquality/projectmanagement/controller/WorkOrderController.java
similarity index 80%
rename from src/main/java/com/waterquality/projectmanagement/controller/c.java
rename to src/main/java/com/waterquality/projectmanagement/controller/WorkOrderController.java
index 576105e..498a0a0 100644
--- a/src/main/java/com/waterquality/projectmanagement/controller/c.java
+++ b/src/main/java/com/waterquality/projectmanagement/controller/WorkOrderController.java
@@ -2,10 +2,11 @@ package com.waterquality.projectmanagement.controller;
import com.waterquality.projectmanagement.Response;
import com.waterquality.projectmanagement.dto.order.*;
+import com.waterquality.projectmanagement.entity.employee.CustomUserDetails;
import com.waterquality.projectmanagement.entity.order.WorkOrderStatus;
import com.waterquality.projectmanagement.service.WorkOrderService;
-import lombok.*;
-import org.apache.catalina.User;
+import lombok.RequiredArgsConstructor;
+import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
@@ -31,9 +32,10 @@ public class WorkOrderController {
@PreAuthorize("hasRole('MAINTENANCE')")
public ResponseEntity> createOrder(
@Valid @RequestBody WorkOrderCreateDTO dto,
- @AuthenticationPrincipal User user) {
+ @AuthenticationPrincipal UserDetails user) {
+ // 假设 UserDetails 实现了 getUsername() 方法返回用户 ID
return ResponseEntity.ok(Response.newSuccess(
- workOrderService.createOrder(dto, user.getId())));
+ workOrderService.createOrder(dto, Integer.valueOf(user.getUsername()))));
}
@PatchMapping("/{id}/status")
@@ -48,10 +50,10 @@ public class WorkOrderController {
@GetMapping("/my-orders")
public ResponseEntity>> getMyOrders(
@RequestParam(required = false) Set statuses,
- @AuthenticationPrincipal User user,
+ @AuthenticationPrincipal CustomUserDetails user,
@PageableDefault Pageable pageable) {
return ResponseEntity.ok(Response.newSuccess(
- workOrderService.getOrdersByAssignee(user.getId(),
+ workOrderService.getOrdersByAssignee(user.getUserID(),
statuses != null ? statuses : EnumSet.allOf(WorkOrderStatus.class),
pageable)));
}
diff --git a/src/main/java/com/waterquality/projectmanagement/dto/EmployeeDTO.java b/src/main/java/com/waterquality/projectmanagement/dto/EmployeeDTO.java
index 9b4801e..8fddea4 100644
--- a/src/main/java/com/waterquality/projectmanagement/dto/EmployeeDTO.java
+++ b/src/main/java/com/waterquality/projectmanagement/dto/EmployeeDTO.java
@@ -14,6 +14,8 @@ public class EmployeeDTO {
private String contact_phone;
private Status status;
private Integer department; // 可以考虑使用简化的部门信息
+ private String password;
+ private String username;
// 可以根据需要添加其他字段
}
\ No newline at end of file
diff --git a/src/main/java/com/waterquality/projectmanagement/dto/login/AuthResponse.java b/src/main/java/com/waterquality/projectmanagement/dto/login/AuthResponse.java
new file mode 100644
index 0000000..cef2ecc
--- /dev/null
+++ b/src/main/java/com/waterquality/projectmanagement/dto/login/AuthResponse.java
@@ -0,0 +1,14 @@
+package com.waterquality.projectmanagement.dto.login;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+// AuthResponse.java
+@Data
+@AllArgsConstructor
+public class AuthResponse {
+ private String token;
+ private Integer employeeId;
+ private String name;
+ private String position;
+}
diff --git a/src/main/java/com/waterquality/projectmanagement/dto/login/LoginDTO.java b/src/main/java/com/waterquality/projectmanagement/dto/login/LoginDTO.java
new file mode 100644
index 0000000..765c45b
--- /dev/null
+++ b/src/main/java/com/waterquality/projectmanagement/dto/login/LoginDTO.java
@@ -0,0 +1,16 @@
+package com.waterquality.projectmanagement.dto.login;
+
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+
+// LoginDTO.java
+@Data
+public class LoginDTO {
+ @NotBlank(message = "用户名不能为空")
+ private String username;
+
+ @NotBlank(message = "密码不能为空")
+ private String password;
+}
+
diff --git a/src/main/java/com/waterquality/projectmanagement/dto/plan/InspectionPlanVO.java b/src/main/java/com/waterquality/projectmanagement/dto/plan/InspectionPlanVO.java
index 1e089f4..b21ba60 100644
--- a/src/main/java/com/waterquality/projectmanagement/dto/plan/InspectionPlanVO.java
+++ b/src/main/java/com/waterquality/projectmanagement/dto/plan/InspectionPlanVO.java
@@ -9,7 +9,7 @@ import java.time.LocalDateTime;
@Data
public class InspectionPlanVO {
private Integer planId;
- private String employeeName;
+ private Integer employeeId;
private String area;
private LocalDateTime plannedTime;
private PlanStatus status;
diff --git a/src/main/java/com/waterquality/projectmanagement/entity/employee/CustomUserDetails.java b/src/main/java/com/waterquality/projectmanagement/entity/employee/CustomUserDetails.java
new file mode 100644
index 0000000..76f026d
--- /dev/null
+++ b/src/main/java/com/waterquality/projectmanagement/entity/employee/CustomUserDetails.java
@@ -0,0 +1,9 @@
+package com.waterquality.projectmanagement.entity.employee;
+
+
+import org.springframework.security.core.userdetails.UserDetails;
+
+public interface CustomUserDetails extends UserDetails {
+ Integer getUserID();
+ String getUsername();
+}
\ No newline at end of file
diff --git a/src/main/java/com/waterquality/projectmanagement/entity/employee/Employee.java b/src/main/java/com/waterquality/projectmanagement/entity/employee/Employee.java
index 11fa603..5a97d19 100644
--- a/src/main/java/com/waterquality/projectmanagement/entity/employee/Employee.java
+++ b/src/main/java/com/waterquality/projectmanagement/entity/employee/Employee.java
@@ -3,17 +3,22 @@ package com.waterquality.projectmanagement.entity.employee;
import com.waterquality.projectmanagement.entity.department.Department;
import jakarta.persistence.*;
import lombok.Data;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+
+import java.util.Collection;
+import java.util.Collections;
@Entity
@Table(name = "employees")
@Data
-public class Employee {
+public class Employee implements CustomUserDetails {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "employee_id")
private Integer employeeId;
- @Column(name = "employee_no",unique = true, nullable = false)
+ @Column(name = "employee_no", unique = true, nullable = false)
private String employeeNo;
private String name;
@@ -31,6 +36,40 @@ public class Employee {
@Column(name = "department_id")
private Integer department;
+ // login
+ @Column(nullable = false, unique = true)
+ private String username;
-}
+ @Column(nullable = false)
+ private String password;
+
+ // 实现 UserDetails 接口方法
+ @Override
+ public Collection extends GrantedAuthority> getAuthorities() {
+ return Collections.singletonList(new SimpleGrantedAuthority("ROLE_" + position.name()));
+ }
+
+ @Override
+ public boolean isAccountNonExpired() { return true; }
+
+ @Override
+ public boolean isAccountNonLocked() { return true; }
+
+ @Override
+ public boolean isCredentialsNonExpired() { return true; }
+
+ @Override
+ public boolean isEnabled() { return status == Status.ACTIVE; }
+
+ // 实现 getUserID 方法
+ @Override
+ public Integer getUserID() {
+ return employeeId;
+ }
+
+ @Override
+ public String getUsername() {
+ return name; // 返回用户名
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/waterquality/projectmanagement/repository/EmployeeRepository.java b/src/main/java/com/waterquality/projectmanagement/repository/EmployeeRepository.java
index cbb9764..f418727 100644
--- a/src/main/java/com/waterquality/projectmanagement/repository/EmployeeRepository.java
+++ b/src/main/java/com/waterquality/projectmanagement/repository/EmployeeRepository.java
@@ -14,10 +14,13 @@ public interface EmployeeRepository extends JpaRepository {
// 根据工号查询
Optional findByEmployeeNo(String employeeNo);
+
Employee findEmployeeByEmployeeId(Integer employeeId);
+
// 按部门查询在职员工
List findByDepartmentAndStatus(Department department, Status status);
Page findByDepartment(Department department, Pageable pageable);
+ Optional