fix: first
This commit is contained in:
commit
94c219f139
|
@ -0,0 +1,62 @@
|
|||
# Compiled class file
|
||||
*.class
|
||||
|
||||
# Eclipse
|
||||
.project
|
||||
.classpath
|
||||
.settings/
|
||||
|
||||
# Intellij
|
||||
*.ipr
|
||||
*.iml
|
||||
*.iws
|
||||
.idea/
|
||||
|
||||
# Maven
|
||||
target/
|
||||
|
||||
# Gradle
|
||||
build
|
||||
.gradle
|
||||
|
||||
# Log file
|
||||
*.log
|
||||
|
||||
# out
|
||||
**/out/
|
||||
|
||||
# BlueJ files
|
||||
*.ctxt
|
||||
|
||||
# Mobile Tools for Java (J2ME)
|
||||
.mtj.tmp/
|
||||
|
||||
# Package Files #
|
||||
*.jar
|
||||
*.war
|
||||
*.nar
|
||||
*.ear
|
||||
*.zip
|
||||
*.tar
|
||||
*.tar.gz
|
||||
*.rar
|
||||
*.pid
|
||||
*.orig
|
||||
|
||||
*.xlsx
|
||||
|
||||
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
|
||||
hs_err_pid*
|
||||
|
||||
# Mac
|
||||
.DS_Store
|
||||
|
||||
*.tmp
|
||||
|
||||
*.puml
|
||||
*.drawio
|
||||
|
||||
**/cert/
|
||||
|
||||
**/webapp/
|
||||
|
|
@ -0,0 +1,242 @@
|
|||
# LDAP管理系统
|
||||
|
||||
一个基于Spring Boot的LDAP目录管理系统,提供用户和组的完整管理功能。
|
||||
|
||||
## 功能特性
|
||||
|
||||
### 用户管理
|
||||
- ✅ 创建、查看、编辑、删除用户
|
||||
- ✅ 用户密码管理
|
||||
- ✅ 用户信息搜索
|
||||
- ✅ 批量用户操作
|
||||
- ✅ 用户统计信息
|
||||
|
||||
### 组管理
|
||||
- ✅ 创建、查看、编辑、删除组
|
||||
- ✅ 组成员管理(添加/移除用户)
|
||||
- ✅ 批量成员操作
|
||||
- ✅ 组搜索功能
|
||||
- ✅ 组统计信息
|
||||
|
||||
### Web界面
|
||||
- ✅ 现代化的响应式Web UI
|
||||
- ✅ 用户友好的操作界面
|
||||
- ✅ 实时数据更新
|
||||
- ✅ 错误处理和用户反馈
|
||||
|
||||
### REST API
|
||||
- ✅ 完整的RESTful API接口
|
||||
- ✅ 统一的响应格式
|
||||
- ✅ 全局异常处理
|
||||
- ✅ API文档和示例
|
||||
|
||||
## 技术栈
|
||||
|
||||
- **后端框架**: Spring Boot 3.2.0
|
||||
- **LDAP集成**: Spring LDAP, Spring Security LDAP
|
||||
- **Web框架**: Spring MVC
|
||||
- **模板引擎**: Thymeleaf
|
||||
- **前端**: Bootstrap 5 + Font Awesome
|
||||
- **构建工具**: Maven
|
||||
- **Java版本**: 17
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 1. 环境要求
|
||||
|
||||
- Java 17+
|
||||
- Maven 3.6+
|
||||
- LDAP服务器(如OpenLDAP、Active Directory等)
|
||||
|
||||
### 2. 配置LDAP连接
|
||||
|
||||
编辑 `src/main/resources/application.yml` 文件:
|
||||
|
||||
```yaml
|
||||
spring:
|
||||
ldap:
|
||||
urls: ldap://localhost:389 # 您的LDAP服务器地址
|
||||
base: dc=example,dc=com # LDAP基础DN
|
||||
username: cn=admin,dc=example,dc=com # 管理员DN
|
||||
password: admin # 管理员密码
|
||||
|
||||
ldap:
|
||||
config:
|
||||
user-search-base: ou=people # 用户搜索基础
|
||||
group-search-base: ou=groups # 组搜索基础
|
||||
# 其他LDAP配置...
|
||||
```
|
||||
|
||||
### 3. 构建和运行
|
||||
|
||||
```bash
|
||||
# 克隆项目
|
||||
git clone <repository-url>
|
||||
cd ldap-demo
|
||||
|
||||
# 构建项目
|
||||
mvn clean compile
|
||||
|
||||
# 运行项目
|
||||
mvn spring-boot:run
|
||||
```
|
||||
|
||||
### 4. 访问应用
|
||||
|
||||
- **Web界面**: http://localhost:8080/ldap-demo/web/
|
||||
- **API接口**: http://localhost:8080/ldap-demo/api/
|
||||
|
||||
## API接口文档
|
||||
|
||||
### 用户管理 API
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
|------|------|------|
|
||||
| GET | `/api/users` | 获取所有用户 |
|
||||
| GET | `/api/users/{username}` | 获取指定用户 |
|
||||
| POST | `/api/users` | 创建新用户 |
|
||||
| PUT | `/api/users/{username}` | 更新用户信息 |
|
||||
| DELETE | `/api/users/{username}` | 删除用户 |
|
||||
| GET | `/api/users/search?keyword={keyword}` | 搜索用户 |
|
||||
| PUT | `/api/users/{username}/password` | 更新用户密码 |
|
||||
| POST | `/api/users/{username}/validate` | 验证用户密码 |
|
||||
| POST | `/api/users/batch` | 批量创建用户 |
|
||||
| GET | `/api/users/statistics` | 获取用户统计 |
|
||||
|
||||
### 组管理 API
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
|------|------|------|
|
||||
| GET | `/api/groups` | 获取所有组 |
|
||||
| GET | `/api/groups/{groupName}` | 获取指定组 |
|
||||
| POST | `/api/groups` | 创建新组 |
|
||||
| PUT | `/api/groups/{groupName}` | 更新组信息 |
|
||||
| DELETE | `/api/groups/{groupName}` | 删除组 |
|
||||
| GET | `/api/groups/search?keyword={keyword}` | 搜索组 |
|
||||
| POST | `/api/groups/{groupName}/members/{username}` | 添加用户到组 |
|
||||
| DELETE | `/api/groups/{groupName}/members/{username}` | 从组中移除用户 |
|
||||
| GET | `/api/groups/{groupName}/members` | 获取组成员 |
|
||||
| GET | `/api/groups/user/{username}` | 获取用户所属组 |
|
||||
| POST | `/api/groups/{groupName}/members/batch` | 批量添加用户到组 |
|
||||
| DELETE | `/api/groups/{groupName}/members/batch` | 批量从组中移除用户 |
|
||||
| GET | `/api/groups/statistics` | 获取组统计 |
|
||||
|
||||
### API请求示例
|
||||
|
||||
#### 创建用户
|
||||
```bash
|
||||
curl -X POST http://localhost:8080/ldap-demo/api/users \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"username": "john.doe",
|
||||
"password": "password123",
|
||||
"commonName": "John Doe",
|
||||
"surname": "Doe",
|
||||
"givenName": "John",
|
||||
"email": "john.doe@example.com",
|
||||
"department": "IT",
|
||||
"title": "Software Engineer"
|
||||
}'
|
||||
```
|
||||
|
||||
#### 创建组
|
||||
```bash
|
||||
curl -X POST http://localhost:8080/ldap-demo/api/groups \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "developers",
|
||||
"description": "Software Development Team",
|
||||
"category": "department"
|
||||
}'
|
||||
```
|
||||
|
||||
#### 添加用户到组
|
||||
```bash
|
||||
curl -X POST http://localhost:8080/ldap-demo/api/groups/developers/members/john.doe
|
||||
```
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
src/
|
||||
├── main/
|
||||
│ ├── java/com/example/ldap/
|
||||
│ │ ├── config/ # 配置类
|
||||
│ │ ├── controller/ # 控制器
|
||||
│ │ ├── dto/ # 数据传输对象
|
||||
│ │ ├── entity/ # 实体类
|
||||
│ │ ├── exception/ # 异常处理
|
||||
│ │ └── service/ # 服务层
|
||||
│ └── resources/
|
||||
│ ├── templates/ # Thymeleaf模板
|
||||
│ └── application.yml # 配置文件
|
||||
└── test/ # 测试代码
|
||||
```
|
||||
|
||||
## 自定义配置
|
||||
|
||||
### LDAP对象类配置
|
||||
|
||||
如果您的LDAP服务器使用不同的对象类,可以在配置文件中修改:
|
||||
|
||||
```yaml
|
||||
ldap:
|
||||
config:
|
||||
user-object-class: inetOrgPerson # 用户对象类
|
||||
group-object-class: groupOfNames # 组对象类
|
||||
```
|
||||
|
||||
### 属性映射配置
|
||||
|
||||
可以根据您的LDAP架构调整属性映射:
|
||||
|
||||
```yaml
|
||||
ldap:
|
||||
config:
|
||||
group-member-attribute: member # 组成员属性
|
||||
group-role-attribute: cn # 组角色属性
|
||||
```
|
||||
|
||||
## 安全考虑
|
||||
|
||||
1. **密码加密**: 用户密码使用BCrypt加密存储
|
||||
2. **LDAP连接**: 支持LDAP连接池配置
|
||||
3. **API安全**: 可以集成Spring Security进行API认证
|
||||
4. **输入验证**: 所有输入都经过验证和清理
|
||||
|
||||
## 故障排除
|
||||
|
||||
### 常见问题
|
||||
|
||||
1. **连接LDAP失败**
|
||||
- 检查LDAP服务器地址和端口
|
||||
- 验证管理员凭据
|
||||
- 确认网络连接
|
||||
|
||||
2. **用户创建失败**
|
||||
- 检查用户DN格式
|
||||
- 验证必填字段
|
||||
- 确认LDAP权限
|
||||
|
||||
3. **组管理错误**
|
||||
- 验证组对象类配置
|
||||
- 检查成员属性设置
|
||||
|
||||
### 日志配置
|
||||
|
||||
在 `application.yml` 中启用详细日志:
|
||||
|
||||
```yaml
|
||||
logging:
|
||||
level:
|
||||
com.example.ldap: DEBUG
|
||||
org.springframework.ldap: DEBUG
|
||||
```
|
||||
|
||||
## 贡献
|
||||
|
||||
欢迎提交问题和功能请求!
|
||||
|
||||
## 许可证
|
||||
|
||||
本项目采用 MIT 许可证。
|
|
@ -0,0 +1,110 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>com.example</groupId>
|
||||
<artifactId>ldap-demo</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>LDAP Management System</name>
|
||||
<description>Spring Boot application for LDAP user and group management</description>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
<java.version>17</java.version>
|
||||
<maven.compiler.source>17</maven.compiler.source>
|
||||
<maven.compiler.target>17</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<!-- Spring Boot Starters -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-ldap</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-thymeleaf</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- LDAP Support -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.ldap</groupId>
|
||||
<artifactId>spring-ldap-core</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-ldap</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Utilities -->
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Embedded LDAP for testing -->
|
||||
<dependency>
|
||||
<groupId>com.unboundid</groupId>
|
||||
<artifactId>unboundid-ldapsdk</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Development Tools -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-devtools</artifactId>
|
||||
<scope>runtime</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- Test Dependencies -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package com.example.ldap;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class LdapDemoApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(LdapDemoApplication.class, args);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
package com.example.ldap.config;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.ldap.core.LdapTemplate;
|
||||
import org.springframework.ldap.core.support.LdapContextSource;
|
||||
|
||||
@Configuration
|
||||
public class LdapConfig {
|
||||
|
||||
@Value("${spring.ldap.urls}")
|
||||
private String ldapUrls;
|
||||
|
||||
@Value("${spring.ldap.base}")
|
||||
private String ldapBase;
|
||||
|
||||
@Value("${spring.ldap.username}")
|
||||
private String ldapUsername;
|
||||
|
||||
@Value("${spring.ldap.password}")
|
||||
private String ldapPassword;
|
||||
|
||||
@Bean
|
||||
public LdapContextSource contextSource() {
|
||||
LdapContextSource contextSource = new LdapContextSource();
|
||||
contextSource.setUrl(ldapUrls);
|
||||
contextSource.setBase(ldapBase);
|
||||
contextSource.setUserDn(ldapUsername);
|
||||
contextSource.setPassword(ldapPassword);
|
||||
|
||||
// 设置连接池参数
|
||||
contextSource.setPooled(true);
|
||||
|
||||
return contextSource;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public LdapTemplate ldapTemplate() {
|
||||
return new LdapTemplate(contextSource());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
package com.example.ldap.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
public class SecurityConfig {
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.authorizeHttpRequests(authz -> authz
|
||||
.requestMatchers("/api/**").permitAll()
|
||||
.requestMatchers("/web/**").permitAll()
|
||||
.requestMatchers("/css/**", "/js/**", "/images/**").permitAll()
|
||||
.requestMatchers("/").permitAll()
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
.csrf(csrf -> csrf.disable())
|
||||
.headers(headers -> headers.frameOptions().deny());
|
||||
|
||||
return http.build();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,206 @@
|
|||
package com.example.ldap.controller;
|
||||
|
||||
import com.example.ldap.dto.ApiResponse;
|
||||
import com.example.ldap.entity.Group;
|
||||
import com.example.ldap.service.GroupService;
|
||||
import jakarta.validation.Valid;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/groups")
|
||||
@CrossOrigin(origins = "*")
|
||||
public class GroupController {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(GroupController.class);
|
||||
|
||||
@Autowired
|
||||
private GroupService groupService;
|
||||
|
||||
/**
|
||||
* 获取所有组
|
||||
*/
|
||||
@GetMapping
|
||||
public ResponseEntity<ApiResponse<List<Group>>> getAllGroups() {
|
||||
logger.info("API: 获取所有组");
|
||||
List<Group> groups = groupService.getAllGroups();
|
||||
return ResponseEntity.ok(ApiResponse.success("获取组列表成功", groups));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据组名获取组
|
||||
*/
|
||||
@GetMapping("/{groupName}")
|
||||
public ResponseEntity<ApiResponse<Group>> getGroupByName(@PathVariable String groupName) {
|
||||
logger.info("API: 获取组 {}", groupName);
|
||||
Group group = groupService.findByName(groupName);
|
||||
if (group == null) {
|
||||
return ResponseEntity.ok(ApiResponse.error("组不存在"));
|
||||
}
|
||||
return ResponseEntity.ok(ApiResponse.success("获取组成功", group));
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建新组
|
||||
*/
|
||||
@PostMapping
|
||||
public ResponseEntity<ApiResponse<Group>> createGroup(@Valid @RequestBody Group group) {
|
||||
logger.info("API: 创建组 {}", group.getName());
|
||||
Group createdGroup = groupService.createGroup(group);
|
||||
return ResponseEntity.ok(ApiResponse.success("组创建成功", createdGroup));
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新组信息
|
||||
*/
|
||||
@PutMapping("/{groupName}")
|
||||
public ResponseEntity<ApiResponse<Group>> updateGroup(
|
||||
@PathVariable String groupName,
|
||||
@RequestBody Group group) {
|
||||
logger.info("API: 更新组 {}", groupName);
|
||||
Group updatedGroup = groupService.updateGroup(groupName, group);
|
||||
return ResponseEntity.ok(ApiResponse.success("组更新成功", updatedGroup));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除组
|
||||
*/
|
||||
@DeleteMapping("/{groupName}")
|
||||
public ResponseEntity<ApiResponse<String>> deleteGroup(@PathVariable String groupName) {
|
||||
logger.info("API: 删除组 {}", groupName);
|
||||
groupService.deleteGroup(groupName);
|
||||
return ResponseEntity.ok(ApiResponse.success("组删除成功"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加用户到组
|
||||
*/
|
||||
@PostMapping("/{groupName}/members/{username}")
|
||||
public ResponseEntity<ApiResponse<String>> addUserToGroup(
|
||||
@PathVariable String groupName,
|
||||
@PathVariable String username) {
|
||||
logger.info("API: 添加用户 {} 到组 {}", username, groupName);
|
||||
groupService.addUserToGroup(groupName, username);
|
||||
return ResponseEntity.ok(ApiResponse.success("用户已添加到组"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 从组中移除用户
|
||||
*/
|
||||
@DeleteMapping("/{groupName}/members/{username}")
|
||||
public ResponseEntity<ApiResponse<String>> removeUserFromGroup(
|
||||
@PathVariable String groupName,
|
||||
@PathVariable String username) {
|
||||
logger.info("API: 从组 {} 移除用户 {}", groupName, username);
|
||||
groupService.removeUserFromGroup(groupName, username);
|
||||
return ResponseEntity.ok(ApiResponse.success("用户已从组中移除"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取组的所有成员
|
||||
*/
|
||||
@GetMapping("/{groupName}/members")
|
||||
public ResponseEntity<ApiResponse<Set<String>>> getGroupMembers(@PathVariable String groupName) {
|
||||
logger.info("API: 获取组 {} 的成员", groupName);
|
||||
Set<String> members = groupService.getGroupMembers(groupName);
|
||||
return ResponseEntity.ok(ApiResponse.success("获取组成员成功", members));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户所属的所有组
|
||||
*/
|
||||
@GetMapping("/user/{username}")
|
||||
public ResponseEntity<ApiResponse<List<Group>>> getUserGroups(@PathVariable String username) {
|
||||
logger.info("API: 获取用户 {} 所属的组", username);
|
||||
List<Group> groups = groupService.getUserGroups(username);
|
||||
return ResponseEntity.ok(ApiResponse.success("获取用户所属组成功", groups));
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索组
|
||||
*/
|
||||
@GetMapping("/search")
|
||||
public ResponseEntity<ApiResponse<List<Group>>> searchGroups(
|
||||
@RequestParam(required = false) String keyword) {
|
||||
logger.info("API: 搜索组 {}", keyword);
|
||||
List<Group> groups = groupService.searchGroups(keyword);
|
||||
return ResponseEntity.ok(ApiResponse.success("搜索完成", groups));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量添加用户到组
|
||||
*/
|
||||
@PostMapping("/{groupName}/members/batch")
|
||||
public ResponseEntity<ApiResponse<String>> addUsersToGroup(
|
||||
@PathVariable String groupName,
|
||||
@RequestBody Map<String, List<String>> data) {
|
||||
logger.info("API: 批量添加用户到组 {}", groupName);
|
||||
|
||||
List<String> usernames = data.get("usernames");
|
||||
if (usernames == null || usernames.isEmpty()) {
|
||||
return ResponseEntity.badRequest()
|
||||
.body(ApiResponse.error("用户名列表不能为空"));
|
||||
}
|
||||
|
||||
for (String username : usernames) {
|
||||
groupService.addUserToGroup(groupName, username);
|
||||
}
|
||||
|
||||
return ResponseEntity.ok(ApiResponse.success("批量添加用户成功"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量从组中移除用户
|
||||
*/
|
||||
@DeleteMapping("/{groupName}/members/batch")
|
||||
public ResponseEntity<ApiResponse<String>> removeUsersFromGroup(
|
||||
@PathVariable String groupName,
|
||||
@RequestBody Map<String, List<String>> data) {
|
||||
logger.info("API: 批量从组 {} 移除用户", groupName);
|
||||
|
||||
List<String> usernames = data.get("usernames");
|
||||
if (usernames == null || usernames.isEmpty()) {
|
||||
return ResponseEntity.badRequest()
|
||||
.body(ApiResponse.error("用户名列表不能为空"));
|
||||
}
|
||||
|
||||
for (String username : usernames) {
|
||||
groupService.removeUserFromGroup(groupName, username);
|
||||
}
|
||||
|
||||
return ResponseEntity.ok(ApiResponse.success("批量移除用户成功"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取组统计信息
|
||||
*/
|
||||
@GetMapping("/statistics")
|
||||
public ResponseEntity<ApiResponse<Map<String, Object>>> getGroupStatistics() {
|
||||
logger.info("API: 获取组统计信息");
|
||||
|
||||
List<Group> allGroups = groupService.getAllGroups();
|
||||
int totalMembers = allGroups.stream()
|
||||
.mapToInt(g -> g.getMembers() != null ? g.getMembers().size() : 0)
|
||||
.sum();
|
||||
|
||||
Map<String, Object> stats = Map.of(
|
||||
"totalGroups", allGroups.size(),
|
||||
"totalMembers", totalMembers,
|
||||
"averageMembersPerGroup", allGroups.isEmpty() ? 0 : (double) totalMembers / allGroups.size(),
|
||||
"groupsWithoutMembers", allGroups.stream()
|
||||
.mapToLong(g -> (g.getMembers() == null || g.getMembers().isEmpty() ||
|
||||
(g.getMembers().size() == 1 && g.getMembers().contains("cn=dummy"))) ? 1 : 0)
|
||||
.sum()
|
||||
);
|
||||
|
||||
return ResponseEntity.ok(ApiResponse.success("获取统计信息成功", stats));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,164 @@
|
|||
package com.example.ldap.controller;
|
||||
|
||||
import com.example.ldap.dto.ApiResponse;
|
||||
import com.example.ldap.entity.User;
|
||||
import com.example.ldap.service.UserService;
|
||||
import jakarta.validation.Valid;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/users")
|
||||
@CrossOrigin(origins = "*")
|
||||
public class UserController {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(UserController.class);
|
||||
|
||||
@Autowired
|
||||
private UserService userService;
|
||||
|
||||
/**
|
||||
* 获取所有用户
|
||||
*/
|
||||
@GetMapping
|
||||
public ResponseEntity<ApiResponse<List<User>>> getAllUsers() {
|
||||
logger.info("API: 获取所有用户");
|
||||
List<User> users = userService.getAllUsers();
|
||||
return ResponseEntity.ok(ApiResponse.success("获取用户列表成功", users));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据用户名获取用户
|
||||
*/
|
||||
@GetMapping("/{username}")
|
||||
public ResponseEntity<ApiResponse<User>> getUserByUsername(@PathVariable String username) {
|
||||
logger.info("API: 获取用户 {}", username);
|
||||
User user = userService.findByUsername(username);
|
||||
if (user == null) {
|
||||
return ResponseEntity.ok(ApiResponse.error("用户不存在"));
|
||||
}
|
||||
return ResponseEntity.ok(ApiResponse.success("获取用户成功", user));
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建新用户
|
||||
*/
|
||||
@PostMapping
|
||||
public ResponseEntity<ApiResponse<User>> createUser(@Valid @RequestBody User user) {
|
||||
logger.info("API: 创建用户 {}", user.getUsername());
|
||||
User createdUser = userService.createUser(user);
|
||||
return ResponseEntity.ok(ApiResponse.success("用户创建成功", createdUser));
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新用户信息
|
||||
*/
|
||||
@PutMapping("/{username}")
|
||||
public ResponseEntity<ApiResponse<User>> updateUser(
|
||||
@PathVariable String username,
|
||||
@RequestBody User user) {
|
||||
logger.info("API: 更新用户 {}", username);
|
||||
User updatedUser = userService.updateUser(username, user);
|
||||
return ResponseEntity.ok(ApiResponse.success("用户更新成功", updatedUser));
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新用户密码
|
||||
*/
|
||||
@PutMapping("/{username}/password")
|
||||
public ResponseEntity<ApiResponse<String>> updatePassword(
|
||||
@PathVariable String username,
|
||||
@RequestBody Map<String, String> passwordData) {
|
||||
logger.info("API: 更新用户密码 {}", username);
|
||||
String newPassword = passwordData.get("newPassword");
|
||||
if (newPassword == null || newPassword.trim().isEmpty()) {
|
||||
return ResponseEntity.badRequest()
|
||||
.body(ApiResponse.error("新密码不能为空"));
|
||||
}
|
||||
|
||||
userService.updatePassword(username, newPassword);
|
||||
return ResponseEntity.ok(ApiResponse.success("密码更新成功"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除用户
|
||||
*/
|
||||
@DeleteMapping("/{username}")
|
||||
public ResponseEntity<ApiResponse<String>> deleteUser(@PathVariable String username) {
|
||||
logger.info("API: 删除用户 {}", username);
|
||||
userService.deleteUser(username);
|
||||
return ResponseEntity.ok(ApiResponse.success("用户删除成功"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索用户
|
||||
*/
|
||||
@GetMapping("/search")
|
||||
public ResponseEntity<ApiResponse<List<User>>> searchUsers(
|
||||
@RequestParam(required = false) String keyword) {
|
||||
logger.info("API: 搜索用户 {}", keyword);
|
||||
List<User> users = userService.searchUsers(keyword);
|
||||
return ResponseEntity.ok(ApiResponse.success("搜索完成", users));
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证用户密码
|
||||
*/
|
||||
@PostMapping("/{username}/validate")
|
||||
public ResponseEntity<ApiResponse<Boolean>> validatePassword(
|
||||
@PathVariable String username,
|
||||
@RequestBody Map<String, String> passwordData) {
|
||||
logger.info("API: 验证用户密码 {}", username);
|
||||
String password = passwordData.get("password");
|
||||
if (password == null) {
|
||||
return ResponseEntity.badRequest()
|
||||
.body(ApiResponse.error("密码不能为空"));
|
||||
}
|
||||
|
||||
boolean isValid = userService.validatePassword(username, password);
|
||||
return ResponseEntity.ok(ApiResponse.success("验证完成", isValid));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量创建用户
|
||||
*/
|
||||
@PostMapping("/batch")
|
||||
public ResponseEntity<ApiResponse<List<User>>> createUsers(@Valid @RequestBody List<User> users) {
|
||||
logger.info("API: 批量创建用户,数量: {}", users.size());
|
||||
|
||||
List<User> createdUsers = users.stream()
|
||||
.map(userService::createUser)
|
||||
.toList();
|
||||
|
||||
return ResponseEntity.ok(ApiResponse.success("批量创建用户成功", createdUsers));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户统计信息
|
||||
*/
|
||||
@GetMapping("/statistics")
|
||||
public ResponseEntity<ApiResponse<Map<String, Object>>> getUserStatistics() {
|
||||
logger.info("API: 获取用户统计信息");
|
||||
|
||||
List<User> allUsers = userService.getAllUsers();
|
||||
Map<String, Object> stats = Map.of(
|
||||
"totalUsers", allUsers.size(),
|
||||
"usersWithEmail", allUsers.stream()
|
||||
.mapToLong(u -> u.getEmail() != null && !u.getEmail().isEmpty() ? 1 : 0)
|
||||
.sum(),
|
||||
"usersWithPhone", allUsers.stream()
|
||||
.mapToLong(u -> u.getPhoneNumber() != null && !u.getPhoneNumber().isEmpty() ? 1 : 0)
|
||||
.sum()
|
||||
);
|
||||
|
||||
return ResponseEntity.ok(ApiResponse.success("获取统计信息成功", stats));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
package com.example.ldap.controller;
|
||||
|
||||
import com.example.ldap.service.GroupService;
|
||||
import com.example.ldap.service.UserService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
||||
@Controller
|
||||
@RequestMapping("/web")
|
||||
public class WebController {
|
||||
|
||||
@Autowired
|
||||
private UserService userService;
|
||||
|
||||
@Autowired
|
||||
private GroupService groupService;
|
||||
|
||||
/**
|
||||
* 主页
|
||||
*/
|
||||
@GetMapping("/")
|
||||
public String index(Model model) {
|
||||
model.addAttribute("userCount", userService.getAllUsers().size());
|
||||
model.addAttribute("groupCount", groupService.getAllGroups().size());
|
||||
return "index";
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户管理页面
|
||||
*/
|
||||
@GetMapping("/users")
|
||||
public String users() {
|
||||
return "users";
|
||||
}
|
||||
|
||||
/**
|
||||
* 组管理页面
|
||||
*/
|
||||
@GetMapping("/groups")
|
||||
public String groups() {
|
||||
return "groups";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
package com.example.ldap.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public class ApiResponse<T> {
|
||||
private boolean success;
|
||||
private String message;
|
||||
private T data;
|
||||
private String error;
|
||||
private long timestamp;
|
||||
|
||||
public ApiResponse() {
|
||||
this.timestamp = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public static <T> ApiResponse<T> success(T data) {
|
||||
ApiResponse<T> response = new ApiResponse<>();
|
||||
response.setSuccess(true);
|
||||
response.setData(data);
|
||||
response.setMessage("操作成功");
|
||||
return response;
|
||||
}
|
||||
|
||||
public static <T> ApiResponse<T> success(String message, T data) {
|
||||
ApiResponse<T> response = new ApiResponse<>();
|
||||
response.setSuccess(true);
|
||||
response.setMessage(message);
|
||||
response.setData(data);
|
||||
return response;
|
||||
}
|
||||
|
||||
public static <T> ApiResponse<T> success(String message) {
|
||||
ApiResponse<T> response = new ApiResponse<>();
|
||||
response.setSuccess(true);
|
||||
response.setMessage(message);
|
||||
return response;
|
||||
}
|
||||
|
||||
public static <T> ApiResponse<T> error(String error) {
|
||||
ApiResponse<T> response = new ApiResponse<>();
|
||||
response.setSuccess(false);
|
||||
response.setError(error);
|
||||
response.setMessage("操作失败");
|
||||
return response;
|
||||
}
|
||||
|
||||
public static <T> ApiResponse<T> error(String message, String error) {
|
||||
ApiResponse<T> response = new ApiResponse<>();
|
||||
response.setSuccess(false);
|
||||
response.setMessage(message);
|
||||
response.setError(error);
|
||||
return response;
|
||||
}
|
||||
|
||||
// Getters and Setters
|
||||
public boolean isSuccess() {
|
||||
return success;
|
||||
}
|
||||
|
||||
public void setSuccess(boolean success) {
|
||||
this.success = success;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public void setMessage(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public T getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public void setData(T data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public String getError() {
|
||||
return error;
|
||||
}
|
||||
|
||||
public void setError(String error) {
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
public long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public void setTimestamp(long timestamp) {
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,104 @@
|
|||
package com.example.ldap.entity;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import org.springframework.ldap.odm.annotations.Attribute;
|
||||
import org.springframework.ldap.odm.annotations.Entry;
|
||||
import org.springframework.ldap.odm.annotations.Id;
|
||||
|
||||
import javax.naming.Name;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
@Entry(base = "ou=groups", objectClasses = {"groupOfNames", "top"})
|
||||
public class Group {
|
||||
|
||||
@Id
|
||||
private Name dn;
|
||||
|
||||
@Attribute(name = "cn")
|
||||
@NotBlank(message = "组名不能为空")
|
||||
@Size(min = 2, max = 50, message = "组名长度必须在2-50个字符之间")
|
||||
private String name;
|
||||
|
||||
@Attribute(name = "description")
|
||||
private String description;
|
||||
|
||||
@Attribute(name = "member")
|
||||
private Set<String> members;
|
||||
|
||||
@Attribute(name = "businessCategory")
|
||||
private String category;
|
||||
|
||||
@Attribute(name = "ou")
|
||||
private String organizationalUnit;
|
||||
|
||||
// 构造函数
|
||||
public Group() {}
|
||||
|
||||
public Group(String name, String description) {
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
// Getters and Setters
|
||||
public Name getDn() {
|
||||
return dn;
|
||||
}
|
||||
|
||||
public void setDn(Name dn) {
|
||||
this.dn = dn;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public Set<String> getMembers() {
|
||||
return members;
|
||||
}
|
||||
|
||||
public void setMembers(Set<String> members) {
|
||||
this.members = members;
|
||||
}
|
||||
|
||||
public String getCategory() {
|
||||
return category;
|
||||
}
|
||||
|
||||
public void setCategory(String category) {
|
||||
this.category = category;
|
||||
}
|
||||
|
||||
public String getOrganizationalUnit() {
|
||||
return organizationalUnit;
|
||||
}
|
||||
|
||||
public void setOrganizationalUnit(String organizationalUnit) {
|
||||
this.organizationalUnit = organizationalUnit;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Group{" +
|
||||
"name='" + name + '\'' +
|
||||
", description='" + description + '\'' +
|
||||
", category='" + category + '\'' +
|
||||
", memberCount=" + (members != null ? members.size() : 0) +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,189 @@
|
|||
package com.example.ldap.entity;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import jakarta.validation.constraints.Email;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import org.springframework.ldap.odm.annotations.Attribute;
|
||||
import org.springframework.ldap.odm.annotations.Entry;
|
||||
import org.springframework.ldap.odm.annotations.Id;
|
||||
|
||||
import javax.naming.Name;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Entry(base = "ou=people", objectClasses = {"inetOrgPerson"})
|
||||
public class User {
|
||||
|
||||
@Id
|
||||
@JsonIgnore // 排除dn字段的JSON序列化,避免序列化错误
|
||||
private Name dn;
|
||||
|
||||
@Attribute(name = "uid")
|
||||
// @NotBlank(message = "用户名不能为空")
|
||||
// @Size(min = 3, max = 20, message = "用户名长度必须在3-20个字符之间")
|
||||
private String username;
|
||||
|
||||
@Attribute(name = "cn")
|
||||
// @NotBlank(message = "姓名不能为空")
|
||||
private String commonName;
|
||||
|
||||
@Attribute(name = "sn")
|
||||
// @NotBlank(message = "姓氏不能为空")
|
||||
private String surname;
|
||||
|
||||
@Attribute(name = "givenName")
|
||||
private String givenName;
|
||||
|
||||
@Attribute(name = "mail")
|
||||
// @Email(message = "邮箱格式不正确")
|
||||
private String email;
|
||||
|
||||
@Attribute(name = "telephoneNumber")
|
||||
private String phoneNumber;
|
||||
|
||||
@Attribute(name = "userPassword")
|
||||
private String password;
|
||||
|
||||
@Attribute(name = "title")
|
||||
private String title;
|
||||
|
||||
@Attribute(name = "departmentNumber")
|
||||
private String department;
|
||||
|
||||
@Attribute(name = "description")
|
||||
private String description;
|
||||
|
||||
@Attribute(name = "employeeNumber")
|
||||
private String employeeNumber;
|
||||
|
||||
@Attribute(name = "memberOf")
|
||||
private List<String> memberOf;
|
||||
|
||||
// 构造函数
|
||||
public User() {}
|
||||
|
||||
public User(String username, String commonName, String surname) {
|
||||
this.username = username;
|
||||
this.commonName = commonName;
|
||||
this.surname = surname;
|
||||
}
|
||||
|
||||
// Getters and Setters
|
||||
public Name getDn() {
|
||||
return dn;
|
||||
}
|
||||
|
||||
public void setDn(Name dn) {
|
||||
this.dn = dn;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public String getCommonName() {
|
||||
return commonName;
|
||||
}
|
||||
|
||||
public void setCommonName(String commonName) {
|
||||
this.commonName = commonName;
|
||||
}
|
||||
|
||||
public String getSurname() {
|
||||
return surname;
|
||||
}
|
||||
|
||||
public void setSurname(String surname) {
|
||||
this.surname = surname;
|
||||
}
|
||||
|
||||
public String getGivenName() {
|
||||
return givenName;
|
||||
}
|
||||
|
||||
public void setGivenName(String givenName) {
|
||||
this.givenName = givenName;
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
|
||||
public void setEmail(String email) {
|
||||
this.email = email;
|
||||
}
|
||||
|
||||
public String getPhoneNumber() {
|
||||
return phoneNumber;
|
||||
}
|
||||
|
||||
public void setPhoneNumber(String phoneNumber) {
|
||||
this.phoneNumber = phoneNumber;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
public String getDepartment() {
|
||||
return department;
|
||||
}
|
||||
|
||||
public void setDepartment(String department) {
|
||||
this.department = department;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public String getEmployeeNumber() {
|
||||
return employeeNumber;
|
||||
}
|
||||
|
||||
public void setEmployeeNumber(String employeeNumber) {
|
||||
this.employeeNumber = employeeNumber;
|
||||
}
|
||||
|
||||
public List<String> getMemberOf() {
|
||||
return memberOf;
|
||||
}
|
||||
|
||||
public void setMemberOf(List<String> memberOf) {
|
||||
this.memberOf = memberOf;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "User{" +
|
||||
"username='" + username + '\'' +
|
||||
", commonName='" + commonName + '\'' +
|
||||
", surname='" + surname + '\'' +
|
||||
", email='" + email + '\'' +
|
||||
", title='" + title + '\'' +
|
||||
", department='" + department + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,109 @@
|
|||
package com.example.ldap.exception;
|
||||
|
||||
import com.example.ldap.dto.ApiResponse;
|
||||
import jakarta.validation.ConstraintViolation;
|
||||
import jakarta.validation.ConstraintViolationException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.validation.BindException;
|
||||
import org.springframework.validation.FieldError;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@RestControllerAdvice
|
||||
public class GlobalExceptionHandler {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
|
||||
|
||||
/**
|
||||
* 处理运行时异常
|
||||
*/
|
||||
@ExceptionHandler(RuntimeException.class)
|
||||
public ResponseEntity<ApiResponse<Object>> handleRuntimeException(RuntimeException e) {
|
||||
logger.error("运行时异常", e);
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
.body(ApiResponse.error("系统错误", e.getMessage()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理参数验证异常
|
||||
*/
|
||||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||
public ResponseEntity<ApiResponse<Object>> handleValidationException(MethodArgumentNotValidException e) {
|
||||
logger.error("参数验证失败", e);
|
||||
|
||||
Map<String, String> errors = new HashMap<>();
|
||||
e.getBindingResult().getAllErrors().forEach((error) -> {
|
||||
String fieldName = ((FieldError) error).getField();
|
||||
String errorMessage = error.getDefaultMessage();
|
||||
errors.put(fieldName, errorMessage);
|
||||
});
|
||||
|
||||
return ResponseEntity.badRequest()
|
||||
.body(ApiResponse.error("参数验证失败", errors.toString()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理绑定异常
|
||||
*/
|
||||
@ExceptionHandler(BindException.class)
|
||||
public ResponseEntity<ApiResponse<Object>> handleBindException(BindException e) {
|
||||
logger.error("参数绑定失败", e);
|
||||
|
||||
Map<String, String> errors = new HashMap<>();
|
||||
e.getBindingResult().getAllErrors().forEach((error) -> {
|
||||
String fieldName = ((FieldError) error).getField();
|
||||
String errorMessage = error.getDefaultMessage();
|
||||
errors.put(fieldName, errorMessage);
|
||||
});
|
||||
|
||||
return ResponseEntity.badRequest()
|
||||
.body(ApiResponse.error("参数绑定失败", errors.toString()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理约束违反异常
|
||||
*/
|
||||
@ExceptionHandler(ConstraintViolationException.class)
|
||||
public ResponseEntity<ApiResponse<Object>> handleConstraintViolationException(ConstraintViolationException e) {
|
||||
logger.error("约束违反", e);
|
||||
|
||||
Map<String, String> errors = new HashMap<>();
|
||||
for (ConstraintViolation<?> violation : e.getConstraintViolations()) {
|
||||
String fieldName = violation.getPropertyPath().toString();
|
||||
String errorMessage = violation.getMessage();
|
||||
errors.put(fieldName, errorMessage);
|
||||
}
|
||||
|
||||
return ResponseEntity.badRequest()
|
||||
.body(ApiResponse.error("约束违反", errors.toString()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理非法参数异常
|
||||
*/
|
||||
@ExceptionHandler(IllegalArgumentException.class)
|
||||
public ResponseEntity<ApiResponse<Object>> handleIllegalArgumentException(IllegalArgumentException e) {
|
||||
logger.error("非法参数", e);
|
||||
return ResponseEntity.badRequest()
|
||||
.body(ApiResponse.error("参数错误", e.getMessage()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理通用异常
|
||||
*/
|
||||
@ExceptionHandler(Exception.class)
|
||||
public ResponseEntity<ApiResponse<Object>> handleGenericException(Exception e) {
|
||||
logger.error("未知异常", e);
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
.body(ApiResponse.error("系统异常", "请联系管理员"));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,309 @@
|
|||
package com.example.ldap.service;
|
||||
|
||||
import com.example.ldap.entity.Group;
|
||||
import com.example.ldap.entity.User;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.ldap.core.support.LdapContextSource;
|
||||
import org.springframework.ldap.core.support.BaseLdapPathContextSource;
|
||||
import org.springframework.ldap.core.LdapTemplate;
|
||||
import org.springframework.ldap.filter.AndFilter;
|
||||
import org.springframework.ldap.filter.EqualsFilter;
|
||||
import org.springframework.ldap.filter.LikeFilter;
|
||||
import org.springframework.ldap.query.LdapQuery;
|
||||
import org.springframework.ldap.query.LdapQueryBuilder;
|
||||
import org.springframework.ldap.support.LdapNameBuilder;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.naming.Name;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
@Service
|
||||
public class GroupService {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(GroupService.class);
|
||||
|
||||
@Autowired
|
||||
private LdapTemplate ldapTemplate;
|
||||
|
||||
@Autowired
|
||||
private UserService userService;
|
||||
|
||||
@Value("${ldap.config.group-search-base}")
|
||||
private String groupSearchBase;
|
||||
|
||||
@Value("${ldap.config.user-search-base}")
|
||||
private String userSearchBase;
|
||||
|
||||
@Value("${spring.ldap.base}")
|
||||
private String ldapBase;
|
||||
|
||||
/**
|
||||
* 获取所有组
|
||||
*/
|
||||
public List<Group> getAllGroups() {
|
||||
logger.info("获取所有组");
|
||||
try {
|
||||
return ldapTemplate.findAll(Group.class);
|
||||
} catch (Exception e) {
|
||||
logger.error("获取所有组失败", e);
|
||||
throw new RuntimeException("获取组列表失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据组名查找组
|
||||
*/
|
||||
public Group findByName(String groupName) {
|
||||
logger.info("查找组: {}", groupName);
|
||||
try {
|
||||
LdapQuery query = LdapQueryBuilder.query()
|
||||
.base(groupSearchBase)
|
||||
.where("objectClass").is("groupOfNames")
|
||||
.and("cn").is(groupName);
|
||||
|
||||
List<Group> groups = ldapTemplate.find(query, Group.class);
|
||||
return groups.isEmpty() ? null : groups.get(0);
|
||||
} catch (Exception e) {
|
||||
logger.error("查找组失败: {}", groupName, e);
|
||||
throw new RuntimeException("查找组失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建新组
|
||||
*/
|
||||
public Group createGroup(Group group) {
|
||||
logger.info("创建组: {}", group.getName());
|
||||
try {
|
||||
if (findByName(group.getName()) != null) {
|
||||
throw new RuntimeException("组名已存在: " + group.getName());
|
||||
}
|
||||
|
||||
// 设置DN
|
||||
Name dn = LdapNameBuilder.newInstance(groupSearchBase)
|
||||
.add("cn", group.getName())
|
||||
.build();
|
||||
group.setDn(dn);
|
||||
|
||||
// 初始化成员集合
|
||||
if (group.getMembers() == null) {
|
||||
group.setMembers(new HashSet<>());
|
||||
}
|
||||
|
||||
// groupOfNames 要求至少有一个成员,添加一个虚拟成员
|
||||
if (group.getMembers().isEmpty()) {
|
||||
group.getMembers().add("cn=dummy");
|
||||
}
|
||||
|
||||
ldapTemplate.create(group);
|
||||
logger.info("组创建成功: {}", group.getName());
|
||||
return group;
|
||||
} catch (Exception e) {
|
||||
logger.error("创建组失败: {}", group.getName(), e);
|
||||
throw new RuntimeException("创建组失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新组信息
|
||||
*/
|
||||
public Group updateGroup(String groupName, Group updatedGroup) {
|
||||
logger.info("更新组: {}", groupName);
|
||||
try {
|
||||
Group existingGroup = findByName(groupName);
|
||||
if (existingGroup == null) {
|
||||
throw new RuntimeException("组不存在: " + groupName);
|
||||
}
|
||||
|
||||
// 更新字段
|
||||
if (updatedGroup.getDescription() != null) {
|
||||
existingGroup.setDescription(updatedGroup.getDescription());
|
||||
}
|
||||
if (updatedGroup.getCategory() != null) {
|
||||
existingGroup.setCategory(updatedGroup.getCategory());
|
||||
}
|
||||
if (updatedGroup.getOrganizationalUnit() != null) {
|
||||
existingGroup.setOrganizationalUnit(updatedGroup.getOrganizationalUnit());
|
||||
}
|
||||
|
||||
ldapTemplate.update(existingGroup);
|
||||
logger.info("组更新成功: {}", groupName);
|
||||
return existingGroup;
|
||||
} catch (Exception e) {
|
||||
logger.error("更新组失败: {}", groupName, e);
|
||||
throw new RuntimeException("更新组失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除组
|
||||
*/
|
||||
public void deleteGroup(String groupName) {
|
||||
logger.info("删除组: {}", groupName);
|
||||
try {
|
||||
Group group = findByName(groupName);
|
||||
if (group == null) {
|
||||
throw new RuntimeException("组不存在: " + groupName);
|
||||
}
|
||||
|
||||
ldapTemplate.delete(group);
|
||||
logger.info("组删除成功: {}", groupName);
|
||||
} catch (Exception e) {
|
||||
logger.error("删除组失败: {}", groupName, e);
|
||||
throw new RuntimeException("删除组失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加用户到组
|
||||
*/
|
||||
public void addUserToGroup(String groupName, String username) {
|
||||
logger.info("添加用户 {} 到组 {}", username, groupName);
|
||||
try {
|
||||
Group group = findByName(groupName);
|
||||
if (group == null) {
|
||||
throw new RuntimeException("组不存在: " + groupName);
|
||||
}
|
||||
|
||||
User user = userService.findByUsername(username);
|
||||
if (user == null) {
|
||||
throw new RuntimeException("用户不存在: " + username);
|
||||
}
|
||||
|
||||
// 构建用户DN
|
||||
String userDn = "uid=" + username + "," + userSearchBase + "," + ((BaseLdapPathContextSource) ldapTemplate.getContextSource()).getBaseLdapPathAsString();
|
||||
|
||||
if (group.getMembers() == null) {
|
||||
group.setMembers(new HashSet<>());
|
||||
}
|
||||
|
||||
// 移除虚拟成员
|
||||
group.getMembers().remove("cn=dummy");
|
||||
|
||||
// 添加用户
|
||||
group.getMembers().add(userDn);
|
||||
|
||||
ldapTemplate.update(group);
|
||||
logger.info("用户 {} 已添加到组 {}", username, groupName);
|
||||
} catch (Exception e) {
|
||||
logger.error("添加用户到组失败: 用户={}, 组={}", username, groupName, e);
|
||||
throw new RuntimeException("添加用户到组失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从组中移除用户
|
||||
*/
|
||||
public void removeUserFromGroup(String groupName, String username) {
|
||||
logger.info("从组 {} 移除用户 {}", groupName, username);
|
||||
try {
|
||||
Group group = findByName(groupName);
|
||||
if (group == null) {
|
||||
throw new RuntimeException("组不存在: " + groupName);
|
||||
}
|
||||
|
||||
// 构建用户DN
|
||||
String userDn = "uid=" + username + "," + userSearchBase + "," + ((BaseLdapPathContextSource) ldapTemplate.getContextSource()).getBaseLdapPathAsString();
|
||||
|
||||
if (group.getMembers() != null) {
|
||||
group.getMembers().remove(userDn);
|
||||
|
||||
// 如果没有成员了,添加虚拟成员
|
||||
if (group.getMembers().isEmpty()) {
|
||||
group.getMembers().add("cn=dummy");
|
||||
}
|
||||
|
||||
ldapTemplate.update(group);
|
||||
logger.info("用户 {} 已从组 {} 移除", username, groupName);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("从组移除用户失败: 用户={}, 组={}", username, groupName, e);
|
||||
throw new RuntimeException("从组移除用户失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取组的所有成员
|
||||
*/
|
||||
public Set<String> getGroupMembers(String groupName) {
|
||||
logger.info("获取组 {} 的成员", groupName);
|
||||
try {
|
||||
Group group = findByName(groupName);
|
||||
if (group == null) {
|
||||
throw new RuntimeException("组不存在: " + groupName);
|
||||
}
|
||||
|
||||
Set<String> members = new HashSet<>();
|
||||
if (group.getMembers() != null) {
|
||||
for (String memberDn : group.getMembers()) {
|
||||
// 提取用户名
|
||||
if (memberDn.startsWith("uid=") && !memberDn.equals("cn=dummy")) {
|
||||
String username = memberDn.substring(4, memberDn.indexOf(","));
|
||||
members.add(username);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return members;
|
||||
} catch (Exception e) {
|
||||
logger.error("获取组成员失败: {}", groupName, e);
|
||||
throw new RuntimeException("获取组成员失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户所属的所有组
|
||||
*/
|
||||
public List<Group> getUserGroups(String username) {
|
||||
logger.info("获取用户 {} 所属的组", username);
|
||||
try {
|
||||
// 构建用户DN
|
||||
String userDn = "uid=" + username + "," + userSearchBase + "," + ((BaseLdapPathContextSource) ldapTemplate.getContextSource()).getBaseLdapPathAsString();
|
||||
|
||||
LdapQuery query = LdapQueryBuilder.query()
|
||||
.base(groupSearchBase)
|
||||
.where("objectClass").is("groupOfNames")
|
||||
.and("member").is(userDn);
|
||||
|
||||
return ldapTemplate.find(query, Group.class);
|
||||
} catch (Exception e) {
|
||||
logger.error("获取用户所属组失败: {}", username, e);
|
||||
throw new RuntimeException("获取用户所属组失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索组
|
||||
*/
|
||||
public List<Group> searchGroups(String keyword) {
|
||||
logger.info("搜索组: {}", keyword);
|
||||
try {
|
||||
LdapQuery query;
|
||||
|
||||
if (keyword != null && !keyword.trim().isEmpty()) {
|
||||
AndFilter filter = new AndFilter();
|
||||
filter.and(new EqualsFilter("objectClass", "groupOfNames"));
|
||||
filter.and(new LikeFilter("cn", "*" + keyword + "*"));
|
||||
|
||||
query = LdapQueryBuilder.query()
|
||||
.base(groupSearchBase)
|
||||
.filter(filter);
|
||||
} else {
|
||||
query = LdapQueryBuilder.query()
|
||||
.base(groupSearchBase)
|
||||
.where("objectClass").is("groupOfNames");
|
||||
}
|
||||
|
||||
return ldapTemplate.find(query, Group.class);
|
||||
} catch (Exception e) {
|
||||
logger.error("搜索组失败: {}", keyword, e);
|
||||
throw new RuntimeException("搜索组失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,255 @@
|
|||
package com.example.ldap.service;
|
||||
|
||||
import com.example.ldap.entity.User;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.ldap.core.LdapTemplate;
|
||||
import org.springframework.ldap.filter.AndFilter;
|
||||
import org.springframework.ldap.filter.EqualsFilter;
|
||||
import org.springframework.ldap.filter.LikeFilter;
|
||||
import org.springframework.ldap.filter.OrFilter;
|
||||
import org.springframework.ldap.query.LdapQuery;
|
||||
import org.springframework.ldap.query.LdapQueryBuilder;
|
||||
import org.springframework.ldap.support.LdapNameBuilder;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.naming.Name;
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class UserService {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(UserService.class);
|
||||
|
||||
@Autowired
|
||||
private LdapTemplate ldapTemplate;
|
||||
|
||||
@Value("${ldap.config.user-search-base}")
|
||||
private String userSearchBase;
|
||||
|
||||
private final BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
|
||||
|
||||
/**
|
||||
* 获取所有用户
|
||||
*/
|
||||
public List<User> getAllUsers() {
|
||||
logger.info("获取所有用户");
|
||||
try {
|
||||
// 使用更具体的查询,避免重复条目问题
|
||||
LdapQuery query = LdapQueryBuilder.query()
|
||||
.base(userSearchBase)
|
||||
.where("objectClass").is("inetOrgPerson")
|
||||
.and("uid").isPresent(); // 只查找有uid属性的用户
|
||||
|
||||
List<User> users = ldapTemplate.find(query, User.class);
|
||||
logger.info("找到 {} 个用户", users.size());
|
||||
return users;
|
||||
} catch (Exception e) {
|
||||
logger.error("获取所有用户失败", e);
|
||||
throw new RuntimeException("获取用户列表失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据用户名查找用户
|
||||
*/
|
||||
public User findByUsername(String username) {
|
||||
logger.info("查找用户: {}", username);
|
||||
try {
|
||||
LdapQuery query = LdapQueryBuilder.query()
|
||||
.base(userSearchBase)
|
||||
.where("objectClass").is("inetOrgPerson")
|
||||
.and("uid").is(username);
|
||||
|
||||
List<User> users = ldapTemplate.find(query, User.class);
|
||||
return users.isEmpty() ? null : users.get(0);
|
||||
} catch (Exception e) {
|
||||
logger.error("查找用户失败: {}", username, e);
|
||||
throw new RuntimeException("查找用户失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建新用户
|
||||
*/
|
||||
public User createUser(User user) {
|
||||
logger.info("创建用户: {}", user.getUsername());
|
||||
try {
|
||||
if (findByUsername(user.getUsername()) != null) {
|
||||
throw new RuntimeException("用户名已存在: " + user.getUsername());
|
||||
}
|
||||
|
||||
// 设置DN
|
||||
Name dn = LdapNameBuilder.newInstance(userSearchBase)
|
||||
.add("uid", user.getUsername())
|
||||
.build();
|
||||
user.setDn(dn);
|
||||
|
||||
// 加密密码
|
||||
if (user.getPassword() != null) {
|
||||
user.setPassword(passwordEncoder.encode(user.getPassword()));
|
||||
}
|
||||
|
||||
// 设置默认值
|
||||
if (user.getCommonName() == null) {
|
||||
user.setCommonName(user.getUsername());
|
||||
}
|
||||
if (user.getSurname() == null) {
|
||||
user.setSurname(user.getUsername());
|
||||
}
|
||||
|
||||
ldapTemplate.create(user);
|
||||
logger.info("用户创建成功: {}", user.getUsername());
|
||||
return user;
|
||||
} catch (Exception e) {
|
||||
logger.error("创建用户失败: {}", user.getUsername(), e);
|
||||
throw new RuntimeException("创建用户失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新用户信息
|
||||
*/
|
||||
public User updateUser(String username, User updatedUser) {
|
||||
logger.info("更新用户: {}", username);
|
||||
try {
|
||||
User existingUser = findByUsername(username);
|
||||
if (existingUser == null) {
|
||||
throw new RuntimeException("用户不存在: " + username);
|
||||
}
|
||||
|
||||
// 更新字段
|
||||
if (updatedUser.getCommonName() != null) {
|
||||
existingUser.setCommonName(updatedUser.getCommonName());
|
||||
}
|
||||
if (updatedUser.getSurname() != null) {
|
||||
existingUser.setSurname(updatedUser.getSurname());
|
||||
}
|
||||
if (updatedUser.getGivenName() != null) {
|
||||
existingUser.setGivenName(updatedUser.getGivenName());
|
||||
}
|
||||
if (updatedUser.getEmail() != null) {
|
||||
existingUser.setEmail(updatedUser.getEmail());
|
||||
}
|
||||
if (updatedUser.getPhoneNumber() != null) {
|
||||
existingUser.setPhoneNumber(updatedUser.getPhoneNumber());
|
||||
}
|
||||
if (updatedUser.getTitle() != null) {
|
||||
existingUser.setTitle(updatedUser.getTitle());
|
||||
}
|
||||
if (updatedUser.getDepartment() != null) {
|
||||
existingUser.setDepartment(updatedUser.getDepartment());
|
||||
}
|
||||
if (updatedUser.getDescription() != null) {
|
||||
existingUser.setDescription(updatedUser.getDescription());
|
||||
}
|
||||
if (updatedUser.getEmployeeNumber() != null) {
|
||||
existingUser.setEmployeeNumber(updatedUser.getEmployeeNumber());
|
||||
}
|
||||
|
||||
ldapTemplate.update(existingUser);
|
||||
logger.info("用户更新成功: {}", username);
|
||||
return existingUser;
|
||||
} catch (Exception e) {
|
||||
logger.error("更新用户失败: {}", username, e);
|
||||
throw new RuntimeException("更新用户失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新用户密码
|
||||
*/
|
||||
public void updatePassword(String username, String newPassword) {
|
||||
logger.info("更新用户密码: {}", username);
|
||||
try {
|
||||
User user = findByUsername(username);
|
||||
if (user == null) {
|
||||
throw new RuntimeException("用户不存在: " + username);
|
||||
}
|
||||
|
||||
user.setPassword(passwordEncoder.encode(newPassword));
|
||||
ldapTemplate.update(user);
|
||||
logger.info("用户密码更新成功: {}", username);
|
||||
} catch (Exception e) {
|
||||
logger.error("更新用户密码失败: {}", username, e);
|
||||
throw new RuntimeException("更新密码失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除用户
|
||||
*/
|
||||
public void deleteUser(String username) {
|
||||
logger.info("删除用户: {}", username);
|
||||
try {
|
||||
User user = findByUsername(username);
|
||||
if (user == null) {
|
||||
throw new RuntimeException("用户不存在: " + username);
|
||||
}
|
||||
|
||||
ldapTemplate.delete(user);
|
||||
logger.info("用户删除成功: {}", username);
|
||||
} catch (Exception e) {
|
||||
logger.error("删除用户失败: {}", username, e);
|
||||
throw new RuntimeException("删除用户失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索用户
|
||||
*/
|
||||
public List<User> searchUsers(String keyword) {
|
||||
logger.info("搜索用户: {}", keyword);
|
||||
try {
|
||||
LdapQuery query;
|
||||
|
||||
if (keyword != null && !keyword.trim().isEmpty()) {
|
||||
// 使用OrFilter进行多字段搜索
|
||||
OrFilter orFilter = new OrFilter();
|
||||
orFilter.or(new LikeFilter("uid", "*" + keyword + "*"));
|
||||
orFilter.or(new LikeFilter("cn", "*" + keyword + "*"));
|
||||
orFilter.or(new LikeFilter("mail", "*" + keyword + "*"));
|
||||
orFilter.or(new LikeFilter("sn", "*" + keyword + "*"));
|
||||
|
||||
AndFilter andFilter = new AndFilter();
|
||||
andFilter.and(new EqualsFilter("objectClass", "inetOrgPerson"));
|
||||
andFilter.and(orFilter);
|
||||
|
||||
query = LdapQueryBuilder.query()
|
||||
.base(userSearchBase)
|
||||
.filter(andFilter);
|
||||
} else {
|
||||
query = LdapQueryBuilder.query()
|
||||
.base(userSearchBase)
|
||||
.where("objectClass").is("inetOrgPerson");
|
||||
}
|
||||
|
||||
return ldapTemplate.find(query, User.class);
|
||||
} catch (Exception e) {
|
||||
logger.error("搜索用户失败: {}", keyword, e);
|
||||
throw new RuntimeException("搜索用户失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证用户密码
|
||||
*/
|
||||
public boolean validatePassword(String username, String password) {
|
||||
logger.info("验证用户密码: {}", username);
|
||||
try {
|
||||
User user = findByUsername(username);
|
||||
if (user == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return passwordEncoder.matches(password, user.getPassword());
|
||||
} catch (Exception e) {
|
||||
logger.error("验证用户密码失败: {}", username, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
server:
|
||||
port: 8080
|
||||
# servlet:
|
||||
# context-path: /ldap-demo
|
||||
|
||||
spring:
|
||||
application:
|
||||
name: ldap-management-system
|
||||
|
||||
# LDAP配置
|
||||
ldap:
|
||||
urls: ldap://localhost:389
|
||||
base: dc=eryajf,dc=net
|
||||
username: cn=admin,dc=eryajf,dc=net
|
||||
password: 123456
|
||||
|
||||
# Thymeleaf配置
|
||||
thymeleaf:
|
||||
cache: false
|
||||
encoding: UTF-8
|
||||
mode: HTML
|
||||
prefix: classpath:/templates/
|
||||
suffix: .html
|
||||
|
||||
# 自定义LDAP配置
|
||||
ldap:
|
||||
config:
|
||||
# 用户DN模板
|
||||
user-dn-pattern: uid={0},ou=people
|
||||
# 用户搜索基础
|
||||
user-search-base: ou=people
|
||||
# 支持uid和cn两种格式的用户搜索
|
||||
user-search-filter: (|(uid={0})(cn={0}))
|
||||
# 组搜索基础 - 设置为空字符串,因为组直接在根目录dc=eryajf,dc=net下
|
||||
group-search-base: ""
|
||||
group-search-filter: (cn={0})
|
||||
group-role-attribute: cn
|
||||
# 组成员属性
|
||||
group-member-attribute: member
|
||||
# 用户对象类
|
||||
user-object-class: inetOrgPerson
|
||||
# 组对象类
|
||||
group-object-class: groupOfNames
|
||||
|
||||
# 日志配置
|
||||
logging:
|
||||
level:
|
||||
com.example.ldap: DEBUG
|
||||
org.springframework.ldap: DEBUG
|
||||
org.springframework.security: DEBUG
|
||||
pattern:
|
||||
console: "%d{yyyy-MM-dd HH:mm:ss} - %msg%n"
|
||||
|
|
@ -0,0 +1,557 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>组管理 - LDAP管理系统</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
|
||||
<style>
|
||||
.table-hover tbody tr:hover {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
.btn-group-sm .btn {
|
||||
padding: 0.25rem 0.5rem;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
.member-badge {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-light">
|
||||
<!-- 导航栏 -->
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
|
||||
<div class="container">
|
||||
<a class="navbar-brand" href="/web/">
|
||||
<i class="fas fa-users-cog me-2"></i>LDAP管理系统
|
||||
</a>
|
||||
<div class="navbar-nav ms-auto">
|
||||
<a class="nav-link" href="/web/users">
|
||||
<i class="fas fa-users me-1"></i>用户管理
|
||||
</a>
|
||||
<a class="nav-link active" href="/web/groups">
|
||||
<i class="fas fa-layer-group me-1"></i>组管理
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- 主要内容 -->
|
||||
<div class="container mt-4">
|
||||
<!-- 页面标题 -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<h2><i class="fas fa-layer-group me-2"></i>组管理</h2>
|
||||
<p class="text-muted">管理LDAP目录中的所有组织结构</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 操作工具栏 -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-8">
|
||||
<div class="input-group">
|
||||
<span class="input-group-text">
|
||||
<i class="fas fa-search"></i>
|
||||
</span>
|
||||
<input type="text" class="form-control" id="searchInput" placeholder="搜索组名或描述...">
|
||||
<button class="btn btn-outline-secondary" type="button" onclick="searchGroups()">搜索</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4 text-end">
|
||||
<button type="button" class="btn btn-success" data-bs-toggle="modal" data-bs-target="#createGroupModal">
|
||||
<i class="fas fa-plus me-1"></i>创建组
|
||||
</button>
|
||||
<button type="button" class="btn btn-secondary" onclick="refreshGroups()">
|
||||
<i class="fas fa-refresh me-1"></i>刷新
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 组列表 -->
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0">组列表</h5>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>组名</th>
|
||||
<th>描述</th>
|
||||
<th>类别</th>
|
||||
<th>成员数量</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="groupTableBody">
|
||||
<!-- 组数据将通过JavaScript动态加载 -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 创建组模态框 -->
|
||||
<div class="modal fade" id="createGroupModal" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">创建新组</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="createGroupForm">
|
||||
<div class="mb-3">
|
||||
<label for="groupName" class="form-label">组名 *</label>
|
||||
<input type="text" class="form-control" id="groupName" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="groupDescription" class="form-label">描述</label>
|
||||
<textarea class="form-control" id="groupDescription" rows="3"></textarea>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="groupCategory" class="form-label">类别</label>
|
||||
<input type="text" class="form-control" id="groupCategory" placeholder="如:部门、项目组等">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="groupOu" class="form-label">组织单位</label>
|
||||
<input type="text" class="form-control" id="groupOu">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
|
||||
<button type="button" class="btn btn-success" onclick="createGroup()">创建组</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 编辑组模态框 -->
|
||||
<div class="modal fade" id="editGroupModal" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">编辑组</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="editGroupForm">
|
||||
<input type="hidden" id="editGroupName">
|
||||
<div class="mb-3">
|
||||
<label for="editGroupDescription" class="form-label">描述</label>
|
||||
<textarea class="form-control" id="editGroupDescription" rows="3"></textarea>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="editGroupCategory" class="form-label">类别</label>
|
||||
<input type="text" class="form-control" id="editGroupCategory">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="editGroupOu" class="form-label">组织单位</label>
|
||||
<input type="text" class="form-control" id="editGroupOu">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
|
||||
<button type="button" class="btn btn-success" onclick="updateGroup()">保存更改</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 管理组成员模态框 -->
|
||||
<div class="modal fade" id="manageMembersModal" tabindex="-1">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">管理组成员</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<input type="hidden" id="currentGroupName">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<h6>当前成员</h6>
|
||||
<div class="border rounded p-3" style="height: 300px; overflow-y: auto;">
|
||||
<div id="currentMembers">
|
||||
<!-- 当前成员列表 -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<h6>添加新成员</h6>
|
||||
<div class="mb-2">
|
||||
<div class="input-group input-group-sm">
|
||||
<input type="text" class="form-control" id="newMemberInput" placeholder="用户名">
|
||||
<button class="btn btn-outline-primary" type="button" onclick="addMember()">
|
||||
<i class="fas fa-plus"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="border rounded p-3" style="height: 250px; overflow-y: auto;">
|
||||
<div id="availableUsers">
|
||||
<!-- 可用用户列表 -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">关闭</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script>
|
||||
// 页面加载时获取组列表
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
loadGroups();
|
||||
});
|
||||
|
||||
// 加载组列表
|
||||
function loadGroups() {
|
||||
fetch('/api/groups')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
displayGroups(data.data);
|
||||
} else {
|
||||
showAlert('获取组列表失败: ' + data.error, 'danger');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
showAlert('网络错误: ' + error.message, 'danger');
|
||||
});
|
||||
}
|
||||
|
||||
// 显示组列表
|
||||
function displayGroups(groups) {
|
||||
const tbody = document.getElementById('groupTableBody');
|
||||
tbody.innerHTML = '';
|
||||
|
||||
groups.forEach(group => {
|
||||
const memberCount = group.members ? group.members.filter(m => m !== 'cn=dummy').length : 0;
|
||||
const row = tbody.insertRow();
|
||||
row.innerHTML = `
|
||||
<td>${group.name || ''}</td>
|
||||
<td>${group.description || ''}</td>
|
||||
<td>${group.category || ''}</td>
|
||||
<td>
|
||||
<span class="badge bg-info member-badge">${memberCount} 人</span>
|
||||
</td>
|
||||
<td>
|
||||
<div class="btn-group btn-group-sm" role="group">
|
||||
<button type="button" class="btn btn-outline-info" onclick="manageMembers('${group.name}')" title="管理成员">
|
||||
<i class="fas fa-users"></i>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-primary" onclick="editGroup('${group.name}')" title="编辑">
|
||||
<i class="fas fa-edit"></i>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-danger" onclick="deleteGroup('${group.name}')" title="删除">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
`;
|
||||
});
|
||||
}
|
||||
|
||||
// 创建组
|
||||
function createGroup() {
|
||||
const groupData = {
|
||||
name: document.getElementById('groupName').value,
|
||||
description: document.getElementById('groupDescription').value,
|
||||
category: document.getElementById('groupCategory').value,
|
||||
organizationalUnit: document.getElementById('groupOu').value
|
||||
};
|
||||
|
||||
fetch('/api/groups', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(groupData)
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
showAlert('组创建成功', 'success');
|
||||
document.getElementById('createGroupForm').reset();
|
||||
bootstrap.Modal.getInstance(document.getElementById('createGroupModal')).hide();
|
||||
loadGroups();
|
||||
} else {
|
||||
showAlert('创建组失败: ' + data.error, 'danger');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
showAlert('网络错误: ' + error.message, 'danger');
|
||||
});
|
||||
}
|
||||
|
||||
// 编辑组
|
||||
function editGroup(groupName) {
|
||||
fetch(`/api/groups/${groupName}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
const group = data.data;
|
||||
document.getElementById('editGroupName').value = group.name;
|
||||
document.getElementById('editGroupDescription').value = group.description || '';
|
||||
document.getElementById('editGroupCategory').value = group.category || '';
|
||||
document.getElementById('editGroupOu').value = group.organizationalUnit || '';
|
||||
|
||||
new bootstrap.Modal(document.getElementById('editGroupModal')).show();
|
||||
} else {
|
||||
showAlert('获取组信息失败: ' + data.error, 'danger');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
showAlert('网络错误: ' + error.message, 'danger');
|
||||
});
|
||||
}
|
||||
|
||||
// 更新组
|
||||
function updateGroup() {
|
||||
const groupName = document.getElementById('editGroupName').value;
|
||||
const groupData = {
|
||||
description: document.getElementById('editGroupDescription').value,
|
||||
category: document.getElementById('editGroupCategory').value,
|
||||
organizationalUnit: document.getElementById('editGroupOu').value
|
||||
};
|
||||
|
||||
fetch(`/api/groups/${groupName}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(groupData)
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
showAlert('组更新成功', 'success');
|
||||
bootstrap.Modal.getInstance(document.getElementById('editGroupModal')).hide();
|
||||
loadGroups();
|
||||
} else {
|
||||
showAlert('更新组失败: ' + data.error, 'danger');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
showAlert('网络错误: ' + error.message, 'danger');
|
||||
});
|
||||
}
|
||||
|
||||
// 删除组
|
||||
function deleteGroup(groupName) {
|
||||
if (confirm(`确定要删除组 "${groupName}" 吗?此操作不可撤销。`)) {
|
||||
fetch(`/api/groups/${groupName}`, {
|
||||
method: 'DELETE'
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
showAlert('组删除成功', 'success');
|
||||
loadGroups();
|
||||
} else {
|
||||
showAlert('删除组失败: ' + data.error, 'danger');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
showAlert('网络错误: ' + error.message, 'danger');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 管理组成员
|
||||
function manageMembers(groupName) {
|
||||
document.getElementById('currentGroupName').value = groupName;
|
||||
|
||||
// 加载当前成员
|
||||
loadCurrentMembers(groupName);
|
||||
|
||||
// 加载所有用户
|
||||
loadAvailableUsers();
|
||||
|
||||
new bootstrap.Modal(document.getElementById('manageMembersModal')).show();
|
||||
}
|
||||
|
||||
// 加载当前成员
|
||||
function loadCurrentMembers(groupName) {
|
||||
fetch(`/api/groups/${groupName}/members`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
const membersDiv = document.getElementById('currentMembers');
|
||||
membersDiv.innerHTML = '';
|
||||
|
||||
if (data.data && data.data.length > 0) {
|
||||
data.data.forEach(member => {
|
||||
const memberDiv = document.createElement('div');
|
||||
memberDiv.className = 'mb-2 d-flex justify-content-between align-items-center';
|
||||
memberDiv.innerHTML = `
|
||||
<span>${member}</span>
|
||||
<button class="btn btn-sm btn-outline-danger" onclick="removeMember('${member}')">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
`;
|
||||
membersDiv.appendChild(memberDiv);
|
||||
});
|
||||
} else {
|
||||
membersDiv.innerHTML = '<p class="text-muted">暂无成员</p>';
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
showAlert('加载成员失败: ' + error.message, 'danger');
|
||||
});
|
||||
}
|
||||
|
||||
// 加载可用用户
|
||||
function loadAvailableUsers() {
|
||||
fetch('/api/users')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
const usersDiv = document.getElementById('availableUsers');
|
||||
usersDiv.innerHTML = '';
|
||||
|
||||
data.data.forEach(user => {
|
||||
const userDiv = document.createElement('div');
|
||||
userDiv.className = 'mb-2 d-flex justify-content-between align-items-center';
|
||||
userDiv.innerHTML = `
|
||||
<span>${user.username} (${user.commonName || ''})</span>
|
||||
<button class="btn btn-sm btn-outline-primary" onclick="addUserToGroup('${user.username}')">
|
||||
<i class="fas fa-plus"></i>
|
||||
</button>
|
||||
`;
|
||||
usersDiv.appendChild(userDiv);
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
showAlert('加载用户失败: ' + error.message, 'danger');
|
||||
});
|
||||
}
|
||||
|
||||
// 添加成员
|
||||
function addMember() {
|
||||
const username = document.getElementById('newMemberInput').value.trim();
|
||||
if (username) {
|
||||
addUserToGroup(username);
|
||||
document.getElementById('newMemberInput').value = '';
|
||||
}
|
||||
}
|
||||
|
||||
// 添加用户到组
|
||||
function addUserToGroup(username) {
|
||||
const groupName = document.getElementById('currentGroupName').value;
|
||||
|
||||
fetch(`/api/groups/${groupName}/members/${username}`, {
|
||||
method: 'POST'
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
showAlert('用户添加成功', 'success');
|
||||
loadCurrentMembers(groupName);
|
||||
} else {
|
||||
showAlert('添加用户失败: ' + data.error, 'danger');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
showAlert('网络错误: ' + error.message, 'danger');
|
||||
});
|
||||
}
|
||||
|
||||
// 移除成员
|
||||
function removeMember(username) {
|
||||
const groupName = document.getElementById('currentGroupName').value;
|
||||
|
||||
if (confirm(`确定要从组中移除用户 "${username}" 吗?`)) {
|
||||
fetch(`/api/groups/${groupName}/members/${username}`, {
|
||||
method: 'DELETE'
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
showAlert('用户移除成功', 'success');
|
||||
loadCurrentMembers(groupName);
|
||||
} else {
|
||||
showAlert('移除用户失败: ' + data.error, 'danger');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
showAlert('网络错误: ' + error.message, 'danger');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 搜索组
|
||||
function searchGroups() {
|
||||
const keyword = document.getElementById('searchInput').value;
|
||||
const url = keyword ? `/api/groups/search?keyword=${encodeURIComponent(keyword)}` : '/api/groups';
|
||||
|
||||
fetch(url)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
displayGroups(data.data);
|
||||
} else {
|
||||
showAlert('搜索失败: ' + data.error, 'danger');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
showAlert('网络错误: ' + error.message, 'danger');
|
||||
});
|
||||
}
|
||||
|
||||
// 刷新组列表
|
||||
function refreshGroups() {
|
||||
document.getElementById('searchInput').value = '';
|
||||
loadGroups();
|
||||
}
|
||||
|
||||
// 显示提示信息
|
||||
function showAlert(message, type) {
|
||||
const alertDiv = document.createElement('div');
|
||||
alertDiv.className = `alert alert-${type} alert-dismissible fade show`;
|
||||
alertDiv.innerHTML = `
|
||||
${message}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||
`;
|
||||
|
||||
const container = document.querySelector('.container');
|
||||
container.insertBefore(alertDiv, container.firstChild);
|
||||
|
||||
// 3秒后自动消失
|
||||
setTimeout(() => {
|
||||
alertDiv.remove();
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
// 回车搜索
|
||||
document.getElementById('searchInput').addEventListener('keypress', function(e) {
|
||||
if (e.key === 'Enter') {
|
||||
searchGroups();
|
||||
}
|
||||
});
|
||||
|
||||
// 回车添加成员
|
||||
document.getElementById('newMemberInput').addEventListener('keypress', function(e) {
|
||||
if (e.key === 'Enter') {
|
||||
addMember();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,169 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>LDAP管理系统</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
|
||||
<style>
|
||||
.card-hover:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.stat-card {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
}
|
||||
.feature-card {
|
||||
border: none;
|
||||
border-radius: 15px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-light">
|
||||
<!-- 导航栏 -->
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
|
||||
<div class="container">
|
||||
<a class="navbar-brand" href="/web/">
|
||||
<i class="fas fa-users-cog me-2"></i>LDAP管理系统
|
||||
</a>
|
||||
<div class="navbar-nav ms-auto">
|
||||
<a class="nav-link" href="/web/users">
|
||||
<i class="fas fa-users me-1"></i>用户管理
|
||||
</a>
|
||||
<a class="nav-link" href="/web/groups">
|
||||
<i class="fas fa-layer-group me-1"></i>组管理
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- 主要内容 -->
|
||||
<div class="container mt-4">
|
||||
<!-- 欢迎信息 -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="jumbotron bg-white p-5 rounded shadow-sm">
|
||||
<h1 class="display-4">
|
||||
<i class="fas fa-home me-3 text-primary"></i>欢迎使用LDAP管理系统
|
||||
</h1>
|
||||
<p class="lead">统一管理您的LDAP用户和组织架构</p>
|
||||
<hr class="my-4">
|
||||
<p>通过这个系统,您可以轻松地管理LDAP目录中的用户和组,执行增删改查等操作。</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 统计卡片 -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-6">
|
||||
<div class="card stat-card card-hover">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<h5 class="card-title">用户总数</h5>
|
||||
<h2 class="mb-0" th:text="${userCount}">0</h2>
|
||||
</div>
|
||||
<div class="text-white-50">
|
||||
<i class="fas fa-users fa-3x"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="card stat-card card-hover">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<h5 class="card-title">组总数</h5>
|
||||
<h2 class="mb-0" th:text="${groupCount}">0</h2>
|
||||
</div>
|
||||
<div class="text-white-50">
|
||||
<i class="fas fa-layer-group fa-3x"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 功能卡片 -->
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-4">
|
||||
<div class="card feature-card card-hover h-100">
|
||||
<div class="card-body text-center">
|
||||
<div class="mb-3">
|
||||
<i class="fas fa-user-plus fa-4x text-primary"></i>
|
||||
</div>
|
||||
<h5 class="card-title">用户管理</h5>
|
||||
<p class="card-text">创建、编辑、删除和搜索LDAP用户,管理用户信息和密码。</p>
|
||||
<a href="/web/users" class="btn btn-primary">
|
||||
<i class="fas fa-arrow-right me-1"></i>进入用户管理
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 mb-4">
|
||||
<div class="card feature-card card-hover h-100">
|
||||
<div class="card-body text-center">
|
||||
<div class="mb-3">
|
||||
<i class="fas fa-sitemap fa-4x text-success"></i>
|
||||
</div>
|
||||
<h5 class="card-title">组管理</h5>
|
||||
<p class="card-text">创建和管理组织结构,添加或移除组成员,查看组关系。</p>
|
||||
<a href="/web/groups" class="btn btn-success">
|
||||
<i class="fas fa-arrow-right me-1"></i>进入组管理
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- API文档链接 -->
|
||||
<div class="row mt-4">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">
|
||||
<i class="fas fa-code me-2"></i>API接口
|
||||
</h5>
|
||||
<p class="card-text">系统提供完整的RESTful API接口,支持程序化调用。</p>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<h6>用户API:</h6>
|
||||
<ul class="list-unstyled">
|
||||
<li><code>GET /api/users</code> - 获取所有用户</li>
|
||||
<li><code>POST /api/users</code> - 创建用户</li>
|
||||
<li><code>PUT /api/users/{username}</code> - 更新用户</li>
|
||||
<li><code>DELETE /api/users/{username}</code> - 删除用户</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<h6>组API:</h6>
|
||||
<ul class="list-unstyled">
|
||||
<li><code>GET /api/groups</code> - 获取所有组</li>
|
||||
<li><code>POST /api/groups</code> - 创建组</li>
|
||||
<li><code>PUT /api/groups/{groupName}</code> - 更新组</li>
|
||||
<li><code>DELETE /api/groups/{groupName}</code> - 删除组</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 页脚 -->
|
||||
<footer class="bg-dark text-white text-center py-3 mt-5">
|
||||
<div class="container">
|
||||
<p class="mb-0">© 2024 LDAP管理系统. 版权所有.</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,500 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>用户管理 - LDAP管理系统</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
|
||||
<style>
|
||||
.table-hover tbody tr:hover {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
.btn-group-sm .btn {
|
||||
padding: 0.25rem 0.5rem;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-light">
|
||||
<!-- 导航栏 -->
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
|
||||
<div class="container">
|
||||
<a class="navbar-brand" href="/web/">
|
||||
<i class="fas fa-users-cog me-2"></i>LDAP管理系统
|
||||
</a>
|
||||
<div class="navbar-nav ms-auto">
|
||||
<a class="nav-link active" href="/web/users">
|
||||
<i class="fas fa-users me-1"></i>用户管理
|
||||
</a>
|
||||
<a class="nav-link" href="/web/groups">
|
||||
<i class="fas fa-layer-group me-1"></i>组管理
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- 主要内容 -->
|
||||
<div class="container mt-4">
|
||||
<!-- 页面标题 -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<h2><i class="fas fa-users me-2"></i>用户管理</h2>
|
||||
<p class="text-muted">管理LDAP目录中的所有用户</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 操作工具栏 -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-8">
|
||||
<div class="input-group">
|
||||
<span class="input-group-text">
|
||||
<i class="fas fa-search"></i>
|
||||
</span>
|
||||
<input type="text" class="form-control" id="searchInput" placeholder="搜索用户名、姓名或邮箱...">
|
||||
<button class="btn btn-outline-secondary" type="button" onclick="searchUsers()">搜索</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4 text-end">
|
||||
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#createUserModal">
|
||||
<i class="fas fa-plus me-1"></i>创建用户
|
||||
</button>
|
||||
<button type="button" class="btn btn-secondary" onclick="refreshUsers()">
|
||||
<i class="fas fa-refresh me-1"></i>刷新
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 用户列表 -->
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0">用户列表</h5>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>用户名</th>
|
||||
<th>姓名</th>
|
||||
<th>邮箱</th>
|
||||
<th>部门</th>
|
||||
<th>职位</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="userTableBody">
|
||||
<!-- 用户数据将通过JavaScript动态加载 -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 创建用户模态框 -->
|
||||
<div class="modal fade" id="createUserModal" tabindex="-1">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">创建新用户</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="createUserForm">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="username" class="form-label">用户名 *</label>
|
||||
<input type="text" class="form-control" id="username" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="password" class="form-label">密码 *</label>
|
||||
<input type="password" class="form-control" id="password" required>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="commonName" class="form-label">姓名 *</label>
|
||||
<input type="text" class="form-control" id="commonName" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="surname" class="form-label">姓氏 *</label>
|
||||
<input type="text" class="form-control" id="surname" required>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="givenName" class="form-label">名字</label>
|
||||
<input type="text" class="form-control" id="givenName">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="email" class="form-label">邮箱</label>
|
||||
<input type="email" class="form-control" id="email">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="phoneNumber" class="form-label">电话</label>
|
||||
<input type="text" class="form-control" id="phoneNumber">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="title" class="form-label">职位</label>
|
||||
<input type="text" class="form-control" id="title">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="department" class="form-label">部门</label>
|
||||
<input type="text" class="form-control" id="department">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="employeeNumber" class="form-label">工号</label>
|
||||
<input type="text" class="form-control" id="employeeNumber">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="description" class="form-label">描述</label>
|
||||
<textarea class="form-control" id="description" rows="3"></textarea>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
|
||||
<button type="button" class="btn btn-primary" onclick="createUser()">创建用户</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 编辑用户模态框 -->
|
||||
<div class="modal fade" id="editUserModal" tabindex="-1">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">编辑用户</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="editUserForm">
|
||||
<input type="hidden" id="editUsername">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="editCommonName" class="form-label">姓名</label>
|
||||
<input type="text" class="form-control" id="editCommonName">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="editSurname" class="form-label">姓氏</label>
|
||||
<input type="text" class="form-control" id="editSurname">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="editGivenName" class="form-label">名字</label>
|
||||
<input type="text" class="form-control" id="editGivenName">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="editEmail" class="form-label">邮箱</label>
|
||||
<input type="email" class="form-control" id="editEmail">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="editPhoneNumber" class="form-label">电话</label>
|
||||
<input type="text" class="form-control" id="editPhoneNumber">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="editTitle" class="form-label">职位</label>
|
||||
<input type="text" class="form-control" id="editTitle">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="editDepartment" class="form-label">部门</label>
|
||||
<input type="text" class="form-control" id="editDepartment">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="editEmployeeNumber" class="form-label">工号</label>
|
||||
<input type="text" class="form-control" id="editEmployeeNumber">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="editDescription" class="form-label">描述</label>
|
||||
<textarea class="form-control" id="editDescription" rows="3"></textarea>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
|
||||
<button type="button" class="btn btn-primary" onclick="updateUser()">保存更改</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script>
|
||||
// 页面加载时获取用户列表
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
loadUsers();
|
||||
});
|
||||
|
||||
// 加载用户列表
|
||||
function loadUsers() {
|
||||
fetch('/api/users')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
displayUsers(data.data);
|
||||
} else {
|
||||
showAlert('获取用户列表失败: ' + data.error, 'danger');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
showAlert('网络错误: ' + error.message, 'danger');
|
||||
});
|
||||
}
|
||||
|
||||
// 显示用户列表
|
||||
function displayUsers(users) {
|
||||
const tbody = document.getElementById('userTableBody');
|
||||
tbody.innerHTML = '';
|
||||
|
||||
users.forEach(user => {
|
||||
const row = tbody.insertRow();
|
||||
row.innerHTML = `
|
||||
<td>${user.username || ''}</td>
|
||||
<td>${user.commonName || ''}</td>
|
||||
<td>${user.email || ''}</td>
|
||||
<td>${user.department || ''}</td>
|
||||
<td>${user.title || ''}</td>
|
||||
<td>
|
||||
<div class="btn-group btn-group-sm" role="group">
|
||||
<button type="button" class="btn btn-outline-primary" onclick="editUser('${user.username}')">
|
||||
<i class="fas fa-edit"></i>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-danger" onclick="deleteUser('${user.username}')">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
`;
|
||||
});
|
||||
}
|
||||
|
||||
// 创建用户
|
||||
function createUser() {
|
||||
const userData = {
|
||||
username: document.getElementById('username').value,
|
||||
password: document.getElementById('password').value,
|
||||
commonName: document.getElementById('commonName').value,
|
||||
surname: document.getElementById('surname').value,
|
||||
givenName: document.getElementById('givenName').value,
|
||||
email: document.getElementById('email').value,
|
||||
phoneNumber: document.getElementById('phoneNumber').value,
|
||||
title: document.getElementById('title').value,
|
||||
department: document.getElementById('department').value,
|
||||
employeeNumber: document.getElementById('employeeNumber').value,
|
||||
description: document.getElementById('description').value
|
||||
};
|
||||
|
||||
fetch('/api/users', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(userData)
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
showAlert('用户创建成功', 'success');
|
||||
document.getElementById('createUserForm').reset();
|
||||
bootstrap.Modal.getInstance(document.getElementById('createUserModal')).hide();
|
||||
loadUsers();
|
||||
} else {
|
||||
showAlert('创建用户失败: ' + data.error, 'danger');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
showAlert('网络错误: ' + error.message, 'danger');
|
||||
});
|
||||
}
|
||||
|
||||
// 编辑用户
|
||||
function editUser(username) {
|
||||
fetch(`/api/users/${username}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
const user = data.data;
|
||||
document.getElementById('editUsername').value = user.username;
|
||||
document.getElementById('editCommonName').value = user.commonName || '';
|
||||
document.getElementById('editSurname').value = user.surname || '';
|
||||
document.getElementById('editGivenName').value = user.givenName || '';
|
||||
document.getElementById('editEmail').value = user.email || '';
|
||||
document.getElementById('editPhoneNumber').value = user.phoneNumber || '';
|
||||
document.getElementById('editTitle').value = user.title || '';
|
||||
document.getElementById('editDepartment').value = user.department || '';
|
||||
document.getElementById('editEmployeeNumber').value = user.employeeNumber || '';
|
||||
document.getElementById('editDescription').value = user.description || '';
|
||||
|
||||
new bootstrap.Modal(document.getElementById('editUserModal')).show();
|
||||
} else {
|
||||
showAlert('获取用户信息失败: ' + data.error, 'danger');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
showAlert('网络错误: ' + error.message, 'danger');
|
||||
});
|
||||
}
|
||||
|
||||
// 更新用户
|
||||
function updateUser() {
|
||||
const username = document.getElementById('editUsername').value;
|
||||
const userData = {
|
||||
commonName: document.getElementById('editCommonName').value,
|
||||
surname: document.getElementById('editSurname').value,
|
||||
givenName: document.getElementById('editGivenName').value,
|
||||
email: document.getElementById('editEmail').value,
|
||||
phoneNumber: document.getElementById('editPhoneNumber').value,
|
||||
title: document.getElementById('editTitle').value,
|
||||
department: document.getElementById('editDepartment').value,
|
||||
employeeNumber: document.getElementById('editEmployeeNumber').value,
|
||||
description: document.getElementById('editDescription').value
|
||||
};
|
||||
|
||||
fetch(`/api/users/${username}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(userData)
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
showAlert('用户更新成功', 'success');
|
||||
bootstrap.Modal.getInstance(document.getElementById('editUserModal')).hide();
|
||||
loadUsers();
|
||||
} else {
|
||||
showAlert('更新用户失败: ' + data.error, 'danger');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
showAlert('网络错误: ' + error.message, 'danger');
|
||||
});
|
||||
}
|
||||
|
||||
// 删除用户
|
||||
function deleteUser(username) {
|
||||
if (confirm(`确定要删除用户 "${username}" 吗?此操作不可撤销。`)) {
|
||||
fetch(`/api/users/${username}`, {
|
||||
method: 'DELETE'
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
showAlert('用户删除成功', 'success');
|
||||
loadUsers();
|
||||
} else {
|
||||
showAlert('删除用户失败: ' + data.error, 'danger');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
showAlert('网络错误: ' + error.message, 'danger');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 搜索用户
|
||||
function searchUsers() {
|
||||
const keyword = document.getElementById('searchInput').value;
|
||||
const url = keyword ? `/api/users/search?keyword=${encodeURIComponent(keyword)}` : '/api/users';
|
||||
|
||||
fetch(url)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
displayUsers(data.data);
|
||||
} else {
|
||||
showAlert('搜索失败: ' + data.error, 'danger');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
showAlert('网络错误: ' + error.message, 'danger');
|
||||
});
|
||||
}
|
||||
|
||||
// 刷新用户列表
|
||||
function refreshUsers() {
|
||||
document.getElementById('searchInput').value = '';
|
||||
loadUsers();
|
||||
}
|
||||
|
||||
// 显示提示信息
|
||||
function showAlert(message, type) {
|
||||
const alertDiv = document.createElement('div');
|
||||
alertDiv.className = `alert alert-${type} alert-dismissible fade show`;
|
||||
alertDiv.innerHTML = `
|
||||
${message}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||
`;
|
||||
|
||||
const container = document.querySelector('.container');
|
||||
container.insertBefore(alertDiv, container.firstChild);
|
||||
|
||||
// 3秒后自动消失
|
||||
setTimeout(() => {
|
||||
alertDiv.remove();
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
// 回车搜索
|
||||
document.getElementById('searchInput').addEventListener('keypress', function(e) {
|
||||
if (e.key === 'Enter') {
|
||||
searchUsers();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in New Issue