Skip to content

Commit

Permalink
新增管理員帳號模型,用於管理員後台
Browse files Browse the repository at this point in the history
  • Loading branch information
GsTio86 committed Nov 12, 2024
1 parent 6da2af9 commit 46cbf88
Show file tree
Hide file tree
Showing 12 changed files with 503 additions and 2 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package me.gt.snaptickets.controller;

import io.swagger.v3.oas.annotations.Hidden;
import io.swagger.v3.oas.annotations.Operation;
import me.gt.snaptickets.dto.AdminUserDto;
import me.gt.snaptickets.model.AdminUser;
import me.gt.snaptickets.service.AdminUserService;
import me.gt.snaptickets.util.PasswordUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.Map;

@CrossOrigin
@RestController
@Hidden
@RequestMapping("/admin")
public class AdminUserController {

@Autowired
private AdminUserService userService;

@Value("${jwt.admin.secret}")
private String jwtSecret;

@Operation(summary = "註冊管理員帳號")
@PostMapping("/auth/register")
public ResponseEntity<String> registerUser(@RequestBody AdminUserDto adminUserDto) {
String validationMessage = PasswordUtil.validatePassword(adminUserDto.getPassword()); // 驗證密碼是否符合安全性要求
if (validationMessage != null) {
return ResponseEntity.badRequest().body(validationMessage);
}
AdminUserService.RegistrationStatus status = userService.registerAdminUser(adminUserDto.convertToUser());
return switch (status) {
case USERNAME_EXISTS -> ResponseEntity.badRequest().body("此帳號已存在");
case EMAIL_EXISTS -> ResponseEntity.badRequest().body("此信箱已被註冊");
default -> ResponseEntity.ok("註冊成功");
};
}

@Operation(summary = "管理員帳號登入")
@PostMapping("/auth/login")
public ResponseEntity<Object> loginUser(@RequestParam String identifier, @RequestParam String password) {
AdminUser user = userService.loginUser(identifier, password);
if (user == null) {
return ResponseEntity.badRequest().body("帳號或密碼錯誤");
}
String token = PasswordUtil.generateJwtToken(user,jwtSecret);
return ResponseEntity.ok().body(Map.of("username", user.getUsername(), "token", token));
}

@Operation(summary = "透過帳號查詢資料")
@GetMapping("/user/info/{username}")
public ResponseEntity<Object> getUserByUsername(@PathVariable String username) {
AdminUser user = userService.getByUsername(username);
if (user == null) {
return ResponseEntity.badRequest().body("查無此帳號");
}
return ResponseEntity.ok(AdminUserDto.fromUser(user));
}

@Operation(summary = "更新帳號資訊")
@PutMapping("/user")
public ResponseEntity<Object> updateUser(@RequestBody AdminUserDto user) {
boolean success = userService.updateUser(user.convertToUser());
if (success) {
return ResponseEntity.ok("更改資料成功");
}
return ResponseEntity.badRequest().body("更改資料失敗");
}

@Operation(summary = "更改帳號密碼")
@PutMapping("/change-password/{username}")
public ResponseEntity<String> updatePassword(@PathVariable String username, @RequestParam String oldPassword, @RequestParam String newPassword) {
String validationMessage = PasswordUtil.validatePassword(newPassword); // 驗證新密碼是否符合安全性要求
if (validationMessage != null) {
return ResponseEntity.badRequest().body(validationMessage);
}
AdminUserService.AuthStatus status = userService.updatePassword(username, oldPassword, newPassword);
return switch (status) {
case USER_NOT_FOUND -> ResponseEntity.badRequest().body("帳號不存在");
case PASSWORD_INCORRECT -> ResponseEntity.badRequest().body("舊密碼不正確");
case PASSWORD_SAME -> ResponseEntity.badRequest().body("新密碼與舊密碼相同");
default -> ResponseEntity.ok("更改密碼成功");
};
}

@Operation(summary = "刪除帳號")
@DeleteMapping("/user/{username}")
public ResponseEntity<String> deleteUser(@PathVariable String username) {
userService.deleteUser(username);
return ResponseEntity.ok("帳號已刪除");
}

}
53 changes: 53 additions & 0 deletions src/main/java/me/gt/snaptickets/dto/AdminUserDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package me.gt.snaptickets.dto;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import me.gt.snaptickets.model.AdminUser;

@Data
@Builder
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class AdminUserDto {

@Schema(description = "帳號")
private String username;

@Schema(description = "密碼 (需包含大小寫字母、數字、特殊字元,且長度至少8)", accessMode = Schema.AccessMode.WRITE_ONLY)
private String password;

@Schema(description = "姓名")
private String name;

@Schema(description = "電子郵件")
private String email;

/**
* 轉換成 AdminUser 物件
* @return AdminUser
*/
public AdminUser convertToUser() {
return AdminUser.builder()
.username(username)
.password(password)
.name(name)
.email(email)
.build();
}

/**
* 從 User 物件轉換成 AdminUserDto 物件
*
* @param user
*
* @return AdminUserDto
*/
public static AdminUserDto fromUser(AdminUser user) {
return AdminUserDto.builder()
.username(user.getUsername())
.name(user.getName())
.email(user.getEmail())
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package me.gt.snaptickets.filter;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import me.gt.snaptickets.service.AdminUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;
import java.util.ArrayList;

public class AdminJwtAuthenticationFilter extends OncePerRequestFilter {

@Value("${jwt.admin.secret}")
private String jwtSecret;

@Autowired
private AdminUserService adminUserService;

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String header = request.getHeader("Admin-Authorization");
if (header != null && header.startsWith("Bearer ")) {
String token = header.substring(7);
try {
Claims claims = Jwts.parser()
.setSigningKey(jwtSecret)
.build()
.parseClaimsJws(token)
.getBody();

String username = claims.getSubject();
String password = (String) claims.get("password");
if (username != null && password != null) {
if (!adminUserService.verifyTokenLogin(username,password)) { // Token 異常 登入密碼不對
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return;
}
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(username, null, new ArrayList<>());
SecurityContextHolder.getContext().setAuthentication(authentication);
}
} catch (Exception e) {
e.printStackTrace();
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return;
}
}
filterChain.doFilter(request, response);
}
}
27 changes: 27 additions & 0 deletions src/main/java/me/gt/snaptickets/mapper/AdminUserMapper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package me.gt.snaptickets.mapper;

import me.gt.snaptickets.model.AdminUser;
import org.apache.ibatis.annotations.*;

@Mapper
public interface AdminUserMapper {

@Insert("INSERT INTO admins (username, password, name, email, permission) " +
"VALUES (#{username}, #{password}, #{name}, #{email}, #{permission})")
void register(AdminUser user);

@Select("SELECT * FROM admins WHERE username = #{username}")
AdminUser getByUsername(String username);

@Select("SELECT * FROM admins WHERE email = #{email}")
AdminUser getByEmail(String email);

@Update("UPDATE admins SET password = #{password} WHERE username = #{username}")
void updatePassword(String username, String password);

@Update("UPDATE admins SET name = #{name}, email = #{email}, permission = #{permission} WHERE username = #{username}")
int updateUser(AdminUser user);

@Delete("DELETE FROM admins WHERE username = #{username}")
void deleteUser(String username);
}
40 changes: 40 additions & 0 deletions src/main/java/me/gt/snaptickets/model/AdminUser.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package me.gt.snaptickets.model;

import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;

import java.time.LocalDateTime;

@Data
@Builder
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class AdminUser {

@Schema(description = "帳號")
private String username;

@Schema(description = "密碼")
@JsonIgnore
private String password;

@Schema(description = "姓名")
private String name;

@Schema(description = "電子郵件")
private String email;

@Schema(description = "權限")
@Builder.Default
private Permission permission = Permission.MOD;

@Schema(description = "建立日期")
@Builder.Default
private LocalDateTime createdAt = LocalDateTime.now();

enum Permission {
ADMIN, MOD
}
}
86 changes: 86 additions & 0 deletions src/main/java/me/gt/snaptickets/service/AdminUserService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package me.gt.snaptickets.service;

import me.gt.snaptickets.model.AdminUser;

public interface AdminUserService {

/**
* 註冊會員
*
* @param adminUser 管理員帳號
* @return 註冊狀態
*/
RegistrationStatus registerAdminUser(AdminUser adminUser);

/**
* 透過管理員名稱查詢管理員
*
* @param identifier 管理員帳號(帳號或信箱)
* @return 管理員資訊
*/
AdminUser loginUser(String identifier, String password);


/**
* 管理員Token登入
*
* @param username 會員名稱
* @param password 會員密碼
* @return 驗證狀態 true: 通過 false: 失敗
*/
boolean verifyTokenLogin(String username, String password);

/**
* 透過管理員名稱查詢管理員
*
* @param username 管理員名稱
* @return 管理員資訊
*/
AdminUser getByUsername(String username);

/**
* 更新管理員資訊
*
* @param adminUser 管理員資訊
*
* @return 更新狀態
*/
boolean updateUser(AdminUser adminUser);

/**
* 更新管理員密碼
*
* @param username 管理員名稱
* @param oldPassword 管理員舊密碼
* @param newPassword 管理員新密碼
*
* @return 帳號狀態
*/
AuthStatus updatePassword(String username, String oldPassword, String newPassword);

/**
* 刪除管理員
*
* @param username 管理員名稱
*/
void deleteUser(String username);

/**
* 註冊狀態
*/
enum RegistrationStatus {
USERNAME_EXISTS,
EMAIL_EXISTS,
SUCCESS
}

/**
* 登入狀態
*/
enum AuthStatus {
USER_NOT_FOUND,
PASSWORD_INCORRECT,
PASSWORD_SAME,
SUCCESS
}
}
9 changes: 9 additions & 0 deletions src/main/java/me/gt/snaptickets/service/UserService.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,15 @@ public interface UserService {
*/
User loginUser(String identifier, String password);

/**
* 會員Token登入
*
* @param username 會員名稱
* @param password 會員密碼
* @return 驗證狀態 true: 通過 false: 失敗
*/
boolean verifyTokenLogin(String username, String password);

/**
* 透過會員名稱查詢會員
*
Expand Down
Loading

0 comments on commit 46cbf88

Please sign in to comment.