Compare commits
33 Commits
9ee8da8999
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d20e8513cc | ||
|
|
50296e8fce | ||
|
|
9609f95e5e | ||
|
|
6e2de46157 | ||
|
|
b479304687 | ||
|
|
1e09ce9f54 | ||
|
|
cc70d867c1 | ||
|
|
3f8dc871ab | ||
|
|
f4070d1b99 | ||
|
|
647d5733ac | ||
|
|
f0d6279949 | ||
|
|
af8959220a | ||
|
|
79ef40bd34 | ||
|
|
2a94f493e6 | ||
|
|
a5a23a6b52 | ||
|
|
ec49ea8e25 | ||
|
|
4f0e0c163d | ||
|
|
b2c6cfe90a | ||
|
|
d4bbaf6715 | ||
|
|
72a1e4d309 | ||
|
|
e23434ab48 | ||
|
|
8dd0efa09e | ||
|
|
e46b820fca | ||
|
|
f3f92b52b8 | ||
|
|
7dc0d26d9b | ||
|
|
7e754b19d4 | ||
|
|
0527602d1c | ||
|
|
8fc7f6554d | ||
|
|
a22a369afa | ||
|
|
da1bdafbb2 | ||
|
|
cbbdc5627e | ||
|
|
2f946ec596 | ||
|
|
6ba72efe80 |
@@ -29,5 +29,21 @@
|
||||
<groupId>com.alibaba.fastjson2</groupId>
|
||||
<artifactId>fastjson2</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-context</artifactId>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.nopj</groupId>
|
||||
<artifactId>chaos_api_domain</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,88 @@
|
||||
package cn.nopj.chaos_api.common.constants;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 全局错误码枚举(HTTP状态码 + 业务错误码)
|
||||
* 规范:
|
||||
* - 4xx: 客户端错误
|
||||
* - 5xx: 服务端错误
|
||||
* - 业务码分段:
|
||||
* 100-199: 用户相关
|
||||
* 200-299: 帖子/评论相关
|
||||
* 300-399: 系统/第三方服务
|
||||
*/
|
||||
@Getter
|
||||
public enum ErrorCode {
|
||||
// ================== 用户相关 (100-199) ==================
|
||||
USERNAME_EXISTS(409, "USER-101", "用户名已存在"),
|
||||
USERNAME_FORMAT_ERROR(400, "USER-102", "用户名需5-20位字母数字组合"),
|
||||
USERNAME_PATTERN_INVALID( 400, "USER-103" , "用户名格式无效,必须由字母、数字、下划线" ),
|
||||
PASSWORD_FORMAT_ERROR(400, "USER-104", "密码需6-20位字符组合"),
|
||||
USER_NOT_EXISTS(404, "USER-104", "用户不存在"),
|
||||
USER_ID_INVALID(400, "USER-105", "用户ID无效"),
|
||||
USER_NOT_EXISTS_OR_PASSWORD_WRONG(401, "USER-105", "用户名不存在或密码错误"),
|
||||
USER_NOT_ENABLED(403, "USER-106", "用户未启用"),
|
||||
USER_NOT_LOGIN(401, "USER-105", "请先登录"),
|
||||
USER_BANNED(403, "USER-106", "账号已被封禁"),
|
||||
EMAIL_EXISTS(409, "USER-107", "邮箱已注册"),
|
||||
EMAIL_FORMAT_ERROR(400, "USER-108", "邮箱格式无效"),
|
||||
PHONE_EXISTS(409, "USER-109", "手机号已注册"),
|
||||
PHONE_FORMAT_ERROR(400, "USER-110", "手机号格式无效"),
|
||||
USER_UPDATE_USERNAME_FAILED(400,"USER-111" , "用户名更新失败"),
|
||||
|
||||
USER_UPDATE_NICKNAME_FAILED( 400, "USER-112", "用户昵称更新失败" ),
|
||||
USER_UPDATE_PROFILE_FAILED(400,"USER-113" , "用户信息更新失败" ),
|
||||
// 用户组
|
||||
ROLE_REVOKE_FAILED(400,"USER-112" ,"用户组删除失败" ),
|
||||
|
||||
ROLE_ID_EXISTS(400, "ROLE-100" , "角色ID已存在" ),
|
||||
ROLE_ID_INVALID(400, "ROLE-101" , "角色ID无效" ),
|
||||
ROLE_ID_NOT_EXISTS(400, "ROLE-102" , "角色ID不存在"),
|
||||
|
||||
// ================== 论坛内容相关 (200-299) ==================
|
||||
POST_NOT_FOUND(404, "POST-201", "帖子不存在"),
|
||||
POST_DELETED(410, "POST-202", "帖子已被删除"),
|
||||
POST_TITLE_EMPTY(400, "POST-203", "标题不能为空"),
|
||||
POST_CONTENT_EMPTY(400, "POST-204", "内容不能为空"),
|
||||
COMMENT_NOT_FOUND(404, "POST-205", "评论不存在"),
|
||||
COMMENT_TOO_LONG(400, "POST-206", "评论超过500字限制"),
|
||||
|
||||
// ================== 系统/第三方 (300-399) ==================
|
||||
SYSTEM_ERROR(500, "SYS-300", "系统错误"),
|
||||
CAPTCHA_ERROR(400, "SYS-301", "验证码错误"),
|
||||
SMS_SEND_FAILED(500, "SYS-302", "短信发送失败"),
|
||||
FILE_UPLOAD_FAILED(500, "SYS-303", "文件上传失败"),
|
||||
RATE_LIMIT_EXCEEDED(429, "SYS-304", "操作过于频繁"),
|
||||
|
||||
// 设备
|
||||
DEVICE_NOT_FOUND(404, "DEVICE-301", "设备不存在"),
|
||||
DEVICE_TYPE_NOT_FOUND(404, "DEVICE-302", "设备类型不存在"),
|
||||
DEVICE_DELETE_FAILED(500, "DEVICE-303", "设备删除失败"),
|
||||
DEVICE_TYPE_DELETE_FAILED(500, "DEVICE-304", "设备类型删除失败"),
|
||||
DEVICE_TYPE_NOT_EMPTY(400, "DEVICE-305", "设备类型不为空"),
|
||||
DEVICE_DISABLED(403, "DEVICE-306", "设备已禁用"),
|
||||
|
||||
// DNS
|
||||
DNS_NOT_DEFINED( 400, "DNS-401" , "未定义的DNS服务器" ),
|
||||
DNS_NOT_FOUND(404, "DNS-402", "DNS服务器不存在"),
|
||||
DNS_DELETE_FAILED(500, "DNS-403", "DNS服务器删除失败"),
|
||||
DNS_NOT_EMPTY(400, "DNS-404", "DNS服务器不为空"),
|
||||
DNS_DISABLED(403, "DNS-405", "DNS服务器已禁用"),
|
||||
DNS_NOT_EXISTS(400, "DNS-406", "DNS服务器不存在"),
|
||||
DNS_NOT_EXISTS_OR_PASSWORD_WRONG(401, "DNS-407", "DNS服务器不存在或密码错误"),
|
||||
DNS_UPDATE_FAILED(400, "DNS-408", "DNS服务器更新失败"),
|
||||
|
||||
// 数据库
|
||||
DB_DNS_NOT_FOUND( 404, "DB-401" , "数据库DNS服务器不存在" );
|
||||
|
||||
private final int httpStatus;
|
||||
private final String code; // 业务错误码(领域-编号)
|
||||
private final String message;
|
||||
|
||||
ErrorCode(int httpStatus, String code, String message) {
|
||||
this.httpStatus = httpStatus;
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package cn.nopj.chaos_api.common.exceotion;
|
||||
|
||||
import cn.nopj.chaos_api.common.constants.ErrorCode;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
public class BizException extends RuntimeException{
|
||||
public final ErrorCode errorCode;
|
||||
|
||||
public BizException(ErrorCode errorCode) {
|
||||
super(errorCode.getMessage());
|
||||
this.errorCode = errorCode;
|
||||
}
|
||||
public BizException(String message) {
|
||||
super(message);
|
||||
this.errorCode = ErrorCode.SYSTEM_ERROR;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package cn.nopj.chaos_api.converter;
|
||||
|
||||
|
||||
import cn.nopj.chaos_api.domain.entity.User;
|
||||
import cn.nopj.chaos_api.dto.request.AuthRegisterRequest;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class UserConverter {
|
||||
public User convert(AuthRegisterRequest request){
|
||||
User user = new User();
|
||||
user.setUsername(request.getUsername());
|
||||
user.setPassword(request.getPassword());
|
||||
user.setEnabled(true);
|
||||
user.setAccountNonExpired(true);
|
||||
user.setCredentialsNonExpired(true);
|
||||
user.setAccountNonLocked(true);
|
||||
return user;
|
||||
}
|
||||
}
|
||||
@@ -48,7 +48,11 @@
|
||||
<dependency>
|
||||
<groupId>org.mariadb.jdbc</groupId>
|
||||
<artifactId>mariadb-java-client</artifactId>
|
||||
<version>3.5.4</version>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-jsqlparser -->
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-jsqlparser</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -1,9 +0,0 @@
|
||||
package cn.nopj.chaos_api.config;
|
||||
|
||||
import org.mybatis.spring.annotation.MapperScan;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
@MapperScan("cn.nopj.chaos_api.mapper")
|
||||
public class MyBatisPlusConfig {
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package cn.nopj.chaos_api.mapper;
|
||||
|
||||
import cn.nopj.chaos_api.domain.entity.Device;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.*;
|
||||
|
||||
@Mapper
|
||||
public interface DeviceMapper extends BaseMapper<Device> {
|
||||
/**
|
||||
* 根据id查询设备信息
|
||||
* @param id 设备id
|
||||
* @return 设备信息
|
||||
*/
|
||||
@Select("SELECT * FROM t_device WHERE id = #{id} AND deleted = 0 ")
|
||||
@Results(id = "DeviceResultMap", value ={
|
||||
@Result(id = true, property = "id", column = "id"),
|
||||
@Result(property = "name", column = "name"),
|
||||
@Result(property = "model", column = "model"),
|
||||
@Result(property = "snmpCommunity", column = "snmp_community"),
|
||||
@Result(property = "manufacturer", column = "manufacturer"),
|
||||
@Result(property = "purchaseDate", column = "purchase_date"),
|
||||
@Result(property = "status", column = "status"),
|
||||
@Result(property = "remark", column = "remark"),
|
||||
@Result(property = "createTime", column = "create_time"),
|
||||
@Result(property = "updateTime", column = "update_time"),
|
||||
@Result(property = "deviceType", column = "type_id",
|
||||
one = @One(select = "cn.nopj.chaos_api.mapper.DriveTypeMapper.selectById"))
|
||||
})
|
||||
Device selectOneWithTypeById(Long id);
|
||||
|
||||
/**
|
||||
* 逻辑删除设备
|
||||
* @param id 设备id
|
||||
* @return 逻辑删除影响行数
|
||||
*/
|
||||
@Update("UPDATE t_device SET deleted = 1 WHERE id = #{id}")
|
||||
int deleteDevice(Long id);
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package cn.nopj.chaos_api.mapper;
|
||||
|
||||
import cn.nopj.chaos_api.domain.entity.DnsServer;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface DnsServerMapper extends BaseMapper<DnsServer> {
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package cn.nopj.chaos_api.mapper;
|
||||
|
||||
import cn.nopj.chaos_api.domain.entity.DeviceType;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface DriveTypeMapper extends BaseMapper<DeviceType> {
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package cn.nopj.chaos_api.mapper;
|
||||
|
||||
import cn.nopj.chaos_api.domain.entity.InterfaceAddressConfig;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface InterfaceAddressConfigMapper extends BaseMapper<InterfaceAddressConfig> {
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package cn.nopj.chaos_api.mapper;
|
||||
|
||||
import cn.nopj.chaos_api.domain.entity.InterfaceDnsMapping;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface InterfaceDnsMappingMapper extends BaseMapper<InterfaceDnsMapping> {
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package cn.nopj.chaos_api.mapper;
|
||||
|
||||
import cn.nopj.chaos_api.domain.entity.NetworkInterface;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface NetworkInterfaceMapper extends BaseMapper<NetworkInterface> {
|
||||
}
|
||||
@@ -3,8 +3,16 @@ package cn.nopj.chaos_api.mapper;
|
||||
import cn.nopj.chaos_api.domain.entity.Role;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
|
||||
@Mapper
|
||||
public interface RoleMapper extends BaseMapper<Role> {
|
||||
|
||||
/**
|
||||
* 根据角色编码查询角色信息
|
||||
* @param code 角色编码
|
||||
* @return 角色信息
|
||||
*/
|
||||
@Select("SELECT * FROM t_role WHERE code = #{code}")
|
||||
Role selectByCode(String code);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
package cn.nopj.chaos_api.mapper;
|
||||
|
||||
import cn.nopj.chaos_api.domain.entity.Role;
|
||||
import cn.nopj.chaos_api.domain.entity.User;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import org.apache.ibatis.annotations.*;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -32,9 +34,108 @@ public interface UserMapper extends BaseMapper<User> {
|
||||
|
||||
/**
|
||||
* 插入用户角色关系
|
||||
* @param id 用户id
|
||||
* @param i 角色id
|
||||
* 注意:已将注解由 @Select 修正为 @Insert,并优化了参数命名。
|
||||
* 由于 t_user_role 现在有自增主键 id,只需插入 user_id 和 role_id。
|
||||
* @param userId 用户id
|
||||
* @param roleId 角色id
|
||||
*/
|
||||
@Select("INSERT INTO t_user_role (user_id, role_id) VALUES (#{userId}, #{roleId})")
|
||||
void insertUserRole(@Param("userId") Long id, @Param("roleId") Long i);
|
||||
@Insert("INSERT INTO t_user_role (user_id, role_id) VALUES (#{userId}, #{roleId})")
|
||||
void insertUserRole(@Param("userId") Long userId, @Param("roleId") Long roleId);
|
||||
|
||||
/**
|
||||
* 根据用户名查询用户信息
|
||||
* @param username 用户名
|
||||
*/
|
||||
@Select("SELECT * FROM t_user WHERE username = #{username}")
|
||||
User selectByUsername(String username);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 根据用户ID查询用户信息和角色信息
|
||||
*/
|
||||
@Select("SELECT * FROM t_user WHERE id = #{id}")
|
||||
@Results({
|
||||
@Result(property = "roles", column = "id",
|
||||
many = @Many(select = "findRolesByUserId"))
|
||||
})
|
||||
User findUserWithRolesById(Long id);
|
||||
|
||||
|
||||
/**
|
||||
* 查询所有用户信息
|
||||
* @return 所有用户信息
|
||||
**/
|
||||
@Select("""
|
||||
SELECT * FROM t_user
|
||||
""")
|
||||
@Results({
|
||||
@Result(id = true, property = "id", column = "id"),
|
||||
@Result(property = "username", column = "username"),
|
||||
@Result(property = "nickname", column = "nickname"),
|
||||
@Result(property = "avatar", column = "avatar"),
|
||||
@Result(property = "roles", column = "id",
|
||||
many = @Many(select = "findRolesByUserId"))
|
||||
})
|
||||
List<User> findAllUserWithRoles();
|
||||
|
||||
|
||||
/**
|
||||
* 根据用户ID查询角色信息
|
||||
*/
|
||||
@Select("""
|
||||
SELECT r.id, r.name ,r.code
|
||||
FROM t_role r
|
||||
JOIN t_user_role ur ON r.id = ur.role_id
|
||||
WHERE ur.user_id = #{userId}
|
||||
""")
|
||||
List<Role> findRolesByUserId(@Param("userId") Long userId);
|
||||
|
||||
@Select("SELECT * FROM t_user WHERE username = #{username} ")
|
||||
@Results({
|
||||
@Result(id = true, property = "id", column = "id"),
|
||||
@Result(property = "username", column = "username"),
|
||||
@Result(property = "nickname", column = "nickname"),
|
||||
@Result(property = "avatar", column = "avatar"),
|
||||
@Result(property = "roles", column = "id",
|
||||
|
||||
many = @Many(select = "findRolesByUserId"))
|
||||
})
|
||||
User findUserWithRolesByUsername(String username);
|
||||
|
||||
/**
|
||||
* 分页查询用户,并携带角色信息
|
||||
* 增加了 roleId 过滤
|
||||
*/
|
||||
@Select("""
|
||||
<script>
|
||||
SELECT * FROM t_user u
|
||||
<where>
|
||||
<if test='keyword != null and keyword != ""'>
|
||||
AND (u.username LIKE CONCAT('%', #{keyword}, '%') OR u.nickname LIKE CONCAT('%', #{keyword}, '%'))
|
||||
</if>
|
||||
|
||||
<if test='roleId != null'>
|
||||
AND EXISTS (
|
||||
SELECT 1
|
||||
FROM t_user_role ur
|
||||
WHERE ur.user_id = u.id
|
||||
AND ur.role_id = #{roleId}
|
||||
)
|
||||
</if>
|
||||
</where>
|
||||
ORDER BY u.id ASC
|
||||
</script>
|
||||
""")
|
||||
@Results({
|
||||
@Result(id = true, property = "id", column = "id"),
|
||||
@Result(property = "username", column = "username"),
|
||||
@Result(property = "nickname", column = "nickname"),
|
||||
@Result(property = "avatar", column = "avatar"),
|
||||
@Result(property = "roles", column = "id",
|
||||
many = @Many(select = "findRolesByUserId"))
|
||||
})
|
||||
IPage<User> selectPageWithRoles(Page<User> page,
|
||||
@Param("keyword") String keyword,
|
||||
@Param("roleId") Long roleId);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package cn.nopj.chaos_api.mapper;
|
||||
|
||||
import cn.nopj.chaos_api.domain.entity.UserRole;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Delete;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
@Mapper
|
||||
public interface UserRoleMapper extends BaseMapper<UserRole> {
|
||||
/**
|
||||
* 删除用户角色关系
|
||||
* @param userId 用户id
|
||||
* @param roleId 角色id
|
||||
*/
|
||||
@Delete("DELETE FROM t_user_role WHERE user_id = #{userId} AND role_id = #{roleId}")
|
||||
int delete(@Param("userId") Long userId,@Param("roleId") Long roleId);
|
||||
}
|
||||
@@ -20,6 +20,10 @@
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus</artifactId>
|
||||
@@ -29,5 +33,11 @@
|
||||
<artifactId>lombok</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>jakarta.validation</groupId>
|
||||
<artifactId>jakarta.validation-api</artifactId>
|
||||
<version>3.0.2</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,30 @@
|
||||
package cn.nopj.chaos_api.domain.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
|
||||
@Data
|
||||
@TableName("t_device")
|
||||
public class Device {
|
||||
@TableId(value = "id",type = IdType.AUTO)
|
||||
private Long id;
|
||||
private String name;
|
||||
private String model;
|
||||
private Long typeId;
|
||||
private Long locationId;
|
||||
private String snmpCommunity;
|
||||
private String manufacturer;
|
||||
private LocalDate purchaseDate;
|
||||
private int status;
|
||||
private String remark;
|
||||
private LocalDate createTime;
|
||||
private LocalDate updateTime;
|
||||
@TableLogic
|
||||
private LocalDate deleteTime;
|
||||
|
||||
@TableField(exist = false)
|
||||
private DeviceType deviceType;
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package cn.nopj.chaos_api.domain.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@TableName("t_device_type")
|
||||
public class DeviceType {
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 设备类型名称
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 设备类型唯一编码
|
||||
*/
|
||||
private String code;
|
||||
|
||||
/**
|
||||
* 父级类型ID (0表示顶级)
|
||||
*/
|
||||
private Long parentId;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
/**
|
||||
* 逻辑删除时间
|
||||
*/
|
||||
@TableLogic
|
||||
private LocalDateTime deleteTime;
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package cn.nopj.chaos_api.domain.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* DNS 服务器实体
|
||||
* 对应表: t_dns_server
|
||||
*/
|
||||
@Data
|
||||
@TableName("t_dns_server")
|
||||
public class DnsServer {
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* DNS服务器IP地址
|
||||
*/
|
||||
private String dnsAddress;
|
||||
|
||||
/**
|
||||
* DNS名称 (如: Google DNS)
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 描述
|
||||
*/
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
/**
|
||||
* 逻辑删除时间
|
||||
*/
|
||||
@TableLogic
|
||||
private LocalDateTime deleteTime;
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package cn.nopj.chaos_api.domain.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 接口地址与VLAN配置实体 (核心配置)
|
||||
* 对应表: t_interface_address_config
|
||||
*/
|
||||
@Data
|
||||
@TableName("t_interface_address_config")
|
||||
public class InterfaceAddressConfig {
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 关联物理接口ID
|
||||
*/
|
||||
private Long interfaceId;
|
||||
|
||||
/**
|
||||
* VLAN ID (0或NULL表示Native/Untagged)
|
||||
*/
|
||||
private Integer vlanId;
|
||||
|
||||
/**
|
||||
* IP地址 (支持IPv4/IPv6)
|
||||
*/
|
||||
private String ipAddress;
|
||||
|
||||
/**
|
||||
* 子网掩码/CIDR前缀
|
||||
*/
|
||||
private String subnetMask;
|
||||
|
||||
/**
|
||||
* 网关IP地址
|
||||
*/
|
||||
private String gatewayIp;
|
||||
|
||||
/**
|
||||
* 广播地址
|
||||
*/
|
||||
private String broadcastAddress;
|
||||
|
||||
/**
|
||||
* 是否为主IP (true:主IP, false:从IP/Alias)
|
||||
*/
|
||||
private Boolean isPrimary;
|
||||
|
||||
/**
|
||||
* 是否启用DHCP (true:启用, false:静态)
|
||||
*/
|
||||
private Boolean isDhcp;
|
||||
|
||||
/**
|
||||
* MTU值
|
||||
*/
|
||||
private Integer mtu;
|
||||
|
||||
/**
|
||||
* 配置状态 (1:启用, 0:禁用)
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
/**
|
||||
* 逻辑删除时间
|
||||
*/
|
||||
@TableLogic
|
||||
private LocalDateTime deleteTime;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package cn.nopj.chaos_api.domain.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 接口配置与DNS关联实体
|
||||
* 对应表: t_interface_dns_mapping
|
||||
*/
|
||||
@Data
|
||||
@TableName("t_interface_dns_mapping")
|
||||
public class InterfaceDnsMapping {
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 接口配置ID (关联 t_interface_address_config)
|
||||
*/
|
||||
private Long configId;
|
||||
|
||||
/**
|
||||
* DNS服务器ID (关联 t_dns_server)
|
||||
*/
|
||||
private Long dnsServerId;
|
||||
|
||||
/**
|
||||
* 优先级 (数值越小优先级越高)
|
||||
*/
|
||||
private Integer priority;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private LocalDateTime createTime;
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package cn.nopj.chaos_api.domain.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 网络接口基础实体 (物理/链路层属性)
|
||||
* 对应表: t_network_interface
|
||||
*/
|
||||
@Data
|
||||
@TableName("t_network_interface")
|
||||
public class NetworkInterface {
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 设备ID
|
||||
*/
|
||||
private Long deviceId;
|
||||
|
||||
/**
|
||||
* 父接口ID (用于子接口/聚合口成员)
|
||||
*/
|
||||
private Long parentId;
|
||||
|
||||
/**
|
||||
* 接口名称 (如: eth0, GE0/0/1)
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 接口类型 (1:物理口, 2:聚合口, 3:虚拟口)
|
||||
*/
|
||||
private Integer type;
|
||||
|
||||
/**
|
||||
* MAC地址
|
||||
*/
|
||||
private String macAddress;
|
||||
|
||||
/**
|
||||
* 物理端口速率 (Mbps)
|
||||
*/
|
||||
private Integer portSpeed;
|
||||
|
||||
/**
|
||||
* 双工模式 (1:全双工, 2:半双工, 3:自适应)
|
||||
*/
|
||||
private Integer duplex;
|
||||
|
||||
/**
|
||||
* 接口运行状态 (1:UP, 0:DOWN)
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
/**
|
||||
* 逻辑删除时间
|
||||
*/
|
||||
@TableLogic
|
||||
private LocalDateTime deleteTime;
|
||||
}
|
||||
@@ -1,21 +1,94 @@
|
||||
package cn.nopj.chaos_api.domain.entity;
|
||||
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableLogic;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 系统管理-用户表
|
||||
* @TableName t_user
|
||||
*/
|
||||
@Data
|
||||
@TableName("t_user")
|
||||
public class User implements Serializable {
|
||||
@TableName(value ="t_user")
|
||||
|
||||
public class User {
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
@TableId
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 用户名/登录名
|
||||
*/
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* 密码(加密存储,建议BCrypt)
|
||||
*/
|
||||
private String password;
|
||||
|
||||
/**
|
||||
* 用户昵称
|
||||
*/
|
||||
private String nickname;
|
||||
|
||||
/**
|
||||
* 头像
|
||||
*/
|
||||
private String avatar;
|
||||
|
||||
/**
|
||||
* 状态 (1:启用, 0:禁用)
|
||||
*/
|
||||
private Boolean enabled;
|
||||
|
||||
/**
|
||||
* 账号未过期 (1:是, 0:否)
|
||||
*/
|
||||
private Boolean accountNonExpired;
|
||||
|
||||
/**
|
||||
* 凭证未过期 (1:是, 0:否)
|
||||
*/
|
||||
private Boolean credentialsNonExpired;
|
||||
|
||||
/**
|
||||
* 账号未锁定 (1:是, 0:否)
|
||||
*/
|
||||
private Boolean accountNonLocked;
|
||||
private LocalDateTime createTime;
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
|
||||
/**
|
||||
* 备注信息
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private Date createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
private Date updateTime;
|
||||
/**
|
||||
* 删除时间
|
||||
*/
|
||||
@TableLogic
|
||||
private LocalDateTime deleteTime;
|
||||
|
||||
|
||||
|
||||
@TableField(exist = false)
|
||||
private List<Role> roles;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package cn.nopj.chaos_api.domain.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@TableName("t_user_role")
|
||||
@Data
|
||||
public class UserRole {
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Long id; // 虚拟字段,数据库可不存
|
||||
|
||||
private Long userId;
|
||||
private Long roleId;
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
package cn.nopj.chaos_api.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 登录请求参数
|
||||
*/
|
||||
@Data
|
||||
public class LoginRequest {
|
||||
private String username;
|
||||
private String password;
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
package cn.nopj.chaos_api.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 注册请求参数
|
||||
*/
|
||||
@Data
|
||||
public class RegisterRequest {
|
||||
private String username;
|
||||
private String password;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package cn.nopj.chaos_api.dto.request;
|
||||
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 登录请求参数
|
||||
*/
|
||||
@Data
|
||||
public class AuthLoginRequest {
|
||||
@Pattern(regexp = "^[a-zA-Z0-9_-]{5,16}$", message = "用户名校验失败")
|
||||
private String username;
|
||||
@Pattern(regexp = "^.{8,16}$", message = "密码长度需为8-16位")
|
||||
private String password;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package cn.nopj.chaos_api.dto.request;
|
||||
|
||||
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 注册请求参数
|
||||
*/
|
||||
@Data
|
||||
public class AuthRegisterRequest {
|
||||
@Pattern(regexp = "^[a-zA-Z0-9_-]{5,16}$", message = "用户名需为5-16位字母、数字、_或-")
|
||||
private String username;
|
||||
//密码不低于8位 不高于16位不限制字符
|
||||
@Pattern(regexp = "^.{8,16}$", message = "密码长度需为8-16位")
|
||||
private String password;
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package cn.nopj.chaos_api.dto.request;
|
||||
|
||||
import lombok.Data;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 创建设备请求对象
|
||||
* <p>
|
||||
* 对应您的命名规范:CreateDeviceRequest
|
||||
* 位于 request 包下
|
||||
*/
|
||||
@Data
|
||||
public class CreateDeviceRequest {
|
||||
|
||||
// --- 设备基础信息 ---
|
||||
|
||||
@NotBlank(message = "设备名称不能为空")
|
||||
private String name;
|
||||
|
||||
@NotNull(message = "设备类型ID不能为空")
|
||||
private Long typeId;
|
||||
|
||||
|
||||
private Long locationId;
|
||||
|
||||
@NotBlank(message = "设备型号不能为空")
|
||||
private String model;
|
||||
|
||||
@NotBlank(message = "设备厂商不能为空")
|
||||
private String manufacturer;
|
||||
|
||||
private String snmpCommunity;
|
||||
|
||||
private LocalDate purchaseDate;
|
||||
|
||||
private String remark;
|
||||
|
||||
@Valid
|
||||
private List<NetworkInterfaceRequest> interfaces;
|
||||
|
||||
|
||||
/**
|
||||
* 网络接口请求参数
|
||||
*/
|
||||
@Data
|
||||
public static class NetworkInterfaceRequest {
|
||||
@NotBlank(message = "接口名称不能为空")
|
||||
private String name;
|
||||
|
||||
@NotNull(message = "接口类型不能为空")
|
||||
private Integer type; // 1:物理口, 2:聚合口, 3:虚拟口
|
||||
|
||||
@Pattern(regexp = "^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$", message = "MAC地址格式不正确")
|
||||
private String macAddress;
|
||||
|
||||
private Integer portSpeed;
|
||||
|
||||
private Integer duplex;
|
||||
|
||||
private String remark;
|
||||
|
||||
@Valid
|
||||
private List<InterfaceAddressConfigRequest> addressConfigs;
|
||||
}
|
||||
|
||||
/**
|
||||
* 地址与VLAN配置请求参数
|
||||
*/
|
||||
@Data
|
||||
public static class InterfaceAddressConfigRequest {
|
||||
|
||||
private Integer vlanId;
|
||||
|
||||
@Pattern(regexp = "^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$", message = "IP地址格式不正确")
|
||||
private String ipAddress;
|
||||
|
||||
private String subnetMask;
|
||||
|
||||
private String gatewayIp;
|
||||
|
||||
private String broadcastAddress;
|
||||
|
||||
private Boolean isPrimary;
|
||||
|
||||
private Boolean isDhcp;
|
||||
|
||||
private Integer mtu;
|
||||
|
||||
private List<String> dnsServers;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package cn.nopj.chaos_api.dto.request;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class DeviceQueryRequest {
|
||||
private Integer pageNum = 1;
|
||||
private Integer pageSize = 10;
|
||||
private Integer type;
|
||||
private String keyword;
|
||||
private String status;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package cn.nopj.chaos_api.dto.request;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class SetUserNicknameRequest {
|
||||
|
||||
@NotBlank(message = "昵称不能为空")
|
||||
private String nickname;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package cn.nopj.chaos_api.dto.request;
|
||||
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class SetUserPasswordRequest {
|
||||
//用户id为数字Long类型
|
||||
@Pattern(regexp = "^[0-9]+$", message = "用户id格式错误")
|
||||
private String userId;
|
||||
@Pattern(regexp = "^.{8,16}$", message = "密码长度需为8-16位")
|
||||
private String password;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package cn.nopj.chaos_api.dto.request;
|
||||
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class SetUserRoleRequest {
|
||||
@Pattern(regexp = "^[0-9]+$", message = "用户id格式错误")
|
||||
private Long userId;
|
||||
@Pattern(regexp = "^[0-9]+$", message = "角色id格式错误")
|
||||
private List<Long> rolesId;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package cn.nopj.chaos_api.dto.request;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
@Data
|
||||
public class UpdateDeviceRequest {
|
||||
@NotBlank(message = "设备id不能为空")
|
||||
private Long id;
|
||||
private String name;
|
||||
private String model;
|
||||
private Long typeId;
|
||||
private Long locationId;
|
||||
private String snmpCommunity;
|
||||
private String manufacturer;
|
||||
private LocalDate purchaseDate;
|
||||
private int status;
|
||||
private String remark;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package cn.nopj.chaos_api.dto.request;
|
||||
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class UpdateUsernameRequest {
|
||||
@Pattern(regexp = "^[a-zA-Z0-9_-]{5,16}$", message = "用户名需为5-16位字母、数字、_或-")
|
||||
private String username;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package cn.nopj.chaos_api.dto.request;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class UserProfileUpdateRequest {
|
||||
private String nickname;
|
||||
private String password;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package cn.nopj.chaos_api.dto.response;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class AuthTokenResponse {
|
||||
private String tokenHead;
|
||||
private String token;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package cn.nopj.chaos_api.dto.response;
|
||||
|
||||
import cn.nopj.chaos_api.domain.entity.Device;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
@Data
|
||||
public class DeviceResponse {
|
||||
|
||||
private Long id;
|
||||
private String name;
|
||||
private String model;
|
||||
private Long typeId;
|
||||
private Long locationId;
|
||||
private String snmpCommunity;
|
||||
private String manufacturer;
|
||||
private LocalDate purchaseDate;
|
||||
private int status;
|
||||
private String remark;
|
||||
|
||||
public DeviceResponse(Device device) {
|
||||
this.id = device.getId();
|
||||
this.name = device.getName();
|
||||
this.model = device.getModel();
|
||||
this.typeId = device.getTypeId();
|
||||
this.locationId = device.getLocationId();
|
||||
this.snmpCommunity = device.getSnmpCommunity();
|
||||
this.manufacturer = device.getManufacturer();
|
||||
this.purchaseDate = device.getPurchaseDate();
|
||||
this.status = device.getStatus();
|
||||
this.remark = device.getRemark();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package cn.nopj.chaos_api.dto.response;
|
||||
|
||||
import cn.nopj.chaos_api.domain.entity.DeviceType;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
public class DeviceTypeResponse {
|
||||
private Long id;
|
||||
private String name;
|
||||
private String code;
|
||||
private Long parentId;
|
||||
private String remark;
|
||||
private LocalDateTime createTime;
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
public DeviceTypeResponse(DeviceType type){
|
||||
this.id = type.getId();
|
||||
this.name = type.getName();
|
||||
this.code = type.getCode();
|
||||
this.parentId = type.getParentId();
|
||||
this.remark = type.getRemark();
|
||||
this.createTime = type.getCreateTime();
|
||||
this.updateTime = type.getUpdateTime();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package cn.nopj.chaos_api.dto.response;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class FileUploadResponse {
|
||||
private String fileName;
|
||||
private String fileDownloadUri;
|
||||
private String ossBucket;
|
||||
private String fileOSSUri;
|
||||
private String fileType;
|
||||
private long size;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package cn.nopj.chaos_api.dto.response;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class OptionResponse {
|
||||
/**
|
||||
* 选项标签
|
||||
*/
|
||||
private String label;
|
||||
|
||||
/**
|
||||
* 值
|
||||
*/
|
||||
private String value;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package cn.nopj.chaos_api.dto.response;
|
||||
|
||||
import cn.nopj.chaos_api.domain.entity.Role;
|
||||
import lombok.Data;
|
||||
@Data
|
||||
public class RoleResponse {
|
||||
private Long id;
|
||||
private String name;
|
||||
|
||||
|
||||
public RoleResponse(Role role){
|
||||
this.id = role.getId();
|
||||
this.name = role.getName();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package cn.nopj.chaos_api.dto.response;
|
||||
|
||||
import cn.nopj.chaos_api.domain.entity.User;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class UserProfileResponse {
|
||||
private Long id;
|
||||
private String username;
|
||||
private String nickname;
|
||||
private String avatar;
|
||||
private List<RoleResponse> roles;
|
||||
|
||||
public UserProfileResponse(User user) {
|
||||
this.id = user.getId();
|
||||
this.username = user.getUsername();
|
||||
this.nickname = user.getNickname();
|
||||
this.avatar = user.getAvatar();
|
||||
this.roles = user.getRoles().stream().map(RoleResponse::new).toList();
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,7 @@
|
||||
<artifactId>chaos_api_domain</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<properties>
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
package cn.nopj.chaos_api.service;
|
||||
|
||||
import cn.nopj.chaos_api.dto.request.CreateDeviceRequest;
|
||||
import cn.nopj.chaos_api.dto.request.DeviceQueryRequest;
|
||||
import cn.nopj.chaos_api.dto.request.UpdateDeviceRequest;
|
||||
import cn.nopj.chaos_api.dto.response.DeviceResponse;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
|
||||
public interface DeviceService {
|
||||
/**
|
||||
* 新建设备信息
|
||||
* @param createDeviceRequest 设备信息
|
||||
* @return 新建设备信息结果
|
||||
*/
|
||||
DeviceResponse createDevice(CreateDeviceRequest createDeviceRequest);
|
||||
|
||||
/**
|
||||
* 根据id查询设备信息
|
||||
* @param id 设备id
|
||||
* @return 设备信息
|
||||
*/
|
||||
DeviceResponse getDeviceById(Long id);
|
||||
|
||||
/**
|
||||
* 删除设备信息 伪删除
|
||||
* @param id 设备id
|
||||
*/
|
||||
void deleteDevice(Long id);
|
||||
|
||||
/**
|
||||
* 删除设备信息 真删除
|
||||
* @param id 设备id
|
||||
*/
|
||||
void deleteDeviceReal(Long id);
|
||||
|
||||
/**
|
||||
* 查询所有设备信息
|
||||
* @return 所有设备信息
|
||||
*/
|
||||
IPage<DeviceResponse> getAllDevices(DeviceQueryRequest request);
|
||||
|
||||
/**
|
||||
* 更新设备信息
|
||||
* @param updateDeviceRequest 设备信息
|
||||
* @return 更新设备信息结果
|
||||
*/
|
||||
DeviceResponse updateDevice(UpdateDeviceRequest updateDeviceRequest);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package cn.nopj.chaos_api.service;
|
||||
|
||||
import cn.nopj.chaos_api.dto.response.DeviceTypeResponse;
|
||||
import cn.nopj.chaos_api.dto.response.OptionResponse;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface DeviceTypeService {
|
||||
/**
|
||||
* 获取所有设备类型
|
||||
* @return 所有设备类型
|
||||
*/
|
||||
List<DeviceTypeResponse> getAllDeviceTypes();
|
||||
|
||||
/**
|
||||
* 获取设备类型选项
|
||||
* @return 设备类型选项
|
||||
*/
|
||||
List<OptionResponse> getDeviceTypeOptions();
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package cn.nopj.chaos_api.service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface DnsServerService {
|
||||
|
||||
/**
|
||||
* 根据域名获取DNS服务器ID
|
||||
* @param dnsAddress 域名
|
||||
* @return DNS服务器ID
|
||||
*/
|
||||
Long getDnsServerIdByName(String dnsAddress);
|
||||
|
||||
/**
|
||||
* 根据域名列表获取DNS服务器ID列表
|
||||
* @param nameList 域名列表
|
||||
* @return DNS服务器ID列表
|
||||
*/
|
||||
List<Long> getDnsServerIdListByNameList(List<String> nameList);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package cn.nopj.chaos_api.service;
|
||||
|
||||
|
||||
import cn.nopj.chaos_api.dto.response.FileUploadResponse;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
public interface ImageService {
|
||||
|
||||
/**
|
||||
* 上传图片
|
||||
* @param fileName 文件名
|
||||
* @param content 文件内容
|
||||
* @return 文件路径
|
||||
*/
|
||||
FileUploadResponse uploadImage(String fileName, InputStream content);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package cn.nopj.chaos_api.service;
|
||||
|
||||
import cn.nopj.chaos_api.dto.response.OptionResponse;
|
||||
import cn.nopj.chaos_api.dto.response.RoleResponse;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface RoleService {
|
||||
/**
|
||||
* 获取所有角色
|
||||
* @return 所有角色
|
||||
*/
|
||||
List<OptionResponse> getAllRoles();
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package cn.nopj.chaos_api.service;
|
||||
|
||||
import cn.nopj.chaos_api.dto.request.UserProfileUpdateRequest;
|
||||
import cn.nopj.chaos_api.dto.response.UserProfileResponse;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 用户信息服务
|
||||
*
|
||||
* @author nopj
|
||||
*/
|
||||
public interface UserProfileService {
|
||||
|
||||
/**
|
||||
* 设置用户密码
|
||||
*
|
||||
* @param id 用户id
|
||||
* @param password 密码
|
||||
*/
|
||||
void setUserPassword(Long id, String password);
|
||||
|
||||
/**
|
||||
* 获取所有用户信息
|
||||
*
|
||||
* @return 所有用户信息
|
||||
*/
|
||||
List<UserProfileResponse> getAllUsers();
|
||||
|
||||
/**
|
||||
* 设置用户密码
|
||||
*
|
||||
* @param userId 用户id
|
||||
* @param password 密码
|
||||
*/
|
||||
void setUserPassword(@Pattern(regexp = "^[0-9]+$", message = "用户id格式错误") String userId, @Pattern(regexp = "^.{8,16}$", message = "密码长度需为8-16位") String password);
|
||||
|
||||
/**
|
||||
* 更新用户名
|
||||
*
|
||||
* @param username 用户名
|
||||
* @param newUsername 新用户名
|
||||
*/
|
||||
void updateUsername(String username, String newUsername);
|
||||
|
||||
/**
|
||||
* 根据用户名查询用户信息
|
||||
*
|
||||
* @param username 用户名
|
||||
* @return 用户信息
|
||||
*/
|
||||
UserProfileResponse findUserWithRolesByUsername(String username);
|
||||
|
||||
/**
|
||||
* 设置用户昵称
|
||||
*
|
||||
* @param username 用户名
|
||||
* @param nickname 昵称
|
||||
* @return 处理结果
|
||||
*/
|
||||
UserProfileResponse setUserNickname(@NotBlank(message = "用户账号不能为空") String username, @NotBlank(message = "昵称不能为空") String nickname);
|
||||
|
||||
/**
|
||||
* 批量设置用户昵称
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param nickname 昵称
|
||||
* @return 处理结果
|
||||
*/
|
||||
UserProfileResponse setUserNickname(@NotBlank(message = "用户ID不能为空") Long userId, @NotBlank(message = "昵称不能为空") String nickname);
|
||||
|
||||
|
||||
/**
|
||||
* 更新用户信息
|
||||
*
|
||||
* @param username 用户名
|
||||
* @param request 用户信息
|
||||
* @return 处理结果
|
||||
*/
|
||||
UserProfileResponse updateUserProfile(String username, UserProfileUpdateRequest request);
|
||||
|
||||
/**
|
||||
* 获取所有用户信息
|
||||
*
|
||||
* @param pageNum 页码
|
||||
* @param pageSize 页大小
|
||||
* @return 所有用户信息
|
||||
*/
|
||||
IPage<UserProfileResponse> getAllUsers(Integer pageNum, Integer pageSize,String keyword,Long roleId);
|
||||
|
||||
/**
|
||||
* 根据用户ID查询用户信息
|
||||
*
|
||||
* @param id 用户ID
|
||||
* @return 用户信息
|
||||
*/
|
||||
UserProfileResponse findUserWithRolesById(Long id);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package cn.nopj.chaos_api.service;
|
||||
|
||||
import cn.nopj.chaos_api.dto.request.SetUserRoleRequest;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 用户角色服务
|
||||
*/
|
||||
public interface UserRoleService {
|
||||
|
||||
/**
|
||||
* 为用户设置角色
|
||||
* @param request 请求参数
|
||||
*/
|
||||
void assignRolesToUser(SetUserRoleRequest request);
|
||||
|
||||
/**
|
||||
* 取消用户角色
|
||||
* @param request 角色id组
|
||||
*/
|
||||
int revokeRolesFromUser(SetUserRoleRequest request);
|
||||
}
|
||||
@@ -50,5 +50,12 @@
|
||||
<artifactId>spring-web</artifactId>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-crypto</artifactId>
|
||||
<version>6.5.6</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -1,13 +0,0 @@
|
||||
package cn.nopj.chaos_api.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
@Configuration
|
||||
public class AppConfig {
|
||||
@Bean
|
||||
public RestTemplate restTemplate() {
|
||||
return new RestTemplate ();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
package cn.nopj.chaos_api.service.impl;
|
||||
|
||||
import cn.nopj.chaos_api.common.constants.ErrorCode;
|
||||
import cn.nopj.chaos_api.common.exceotion.BizException;
|
||||
import cn.nopj.chaos_api.domain.entity.Device;
|
||||
import cn.nopj.chaos_api.domain.entity.InterfaceAddressConfig;
|
||||
import cn.nopj.chaos_api.domain.entity.InterfaceDnsMapping;
|
||||
import cn.nopj.chaos_api.domain.entity.NetworkInterface;
|
||||
import cn.nopj.chaos_api.dto.request.CreateDeviceRequest;
|
||||
import cn.nopj.chaos_api.dto.request.DeviceQueryRequest;
|
||||
import cn.nopj.chaos_api.dto.request.UpdateDeviceRequest;
|
||||
import cn.nopj.chaos_api.dto.response.DeviceResponse;
|
||||
import cn.nopj.chaos_api.mapper.DeviceMapper;
|
||||
import cn.nopj.chaos_api.mapper.InterfaceAddressConfigMapper;
|
||||
import cn.nopj.chaos_api.mapper.InterfaceDnsMappingMapper;
|
||||
import cn.nopj.chaos_api.mapper.NetworkInterfaceMapper;
|
||||
import cn.nopj.chaos_api.service.DeviceService;
|
||||
import cn.nopj.chaos_api.service.DnsServerService;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
public class DeviceServiceImpl extends ServiceImpl<DeviceMapper, Device> implements DeviceService {
|
||||
@Autowired
|
||||
NetworkInterfaceMapper networkInterfaceMapper;
|
||||
@Autowired
|
||||
InterfaceAddressConfigMapper interfaceAddressConfigMapper;
|
||||
@Autowired
|
||||
DnsServerService dnsServerService;
|
||||
|
||||
@Autowired
|
||||
InterfaceDnsMappingMapper interfaceDnsMappingMapper;
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public DeviceResponse createDevice(CreateDeviceRequest createDeviceRequest) {
|
||||
Device device = new Device();
|
||||
BeanUtils.copyProperties(createDeviceRequest, device);
|
||||
int rows = this.baseMapper.insert(device);
|
||||
|
||||
|
||||
List<CreateDeviceRequest.NetworkInterfaceRequest> interfaces = createDeviceRequest.getInterfaces();
|
||||
|
||||
if (interfaces == null || interfaces.isEmpty()) {
|
||||
return new DeviceResponse(device);
|
||||
}
|
||||
|
||||
|
||||
interfaces.forEach(i -> {
|
||||
NetworkInterface networkInterface = new NetworkInterface();
|
||||
networkInterface.setDeviceId(device.getId());
|
||||
networkInterface.setName(i.getName());
|
||||
networkInterface.setType(i.getType());
|
||||
networkInterface.setMacAddress(i.getMacAddress());
|
||||
networkInterface.setPortSpeed(i.getPortSpeed());
|
||||
networkInterface.setDuplex(i.getDuplex());
|
||||
networkInterface.setRemark(i.getRemark());
|
||||
networkInterfaceMapper.insert(networkInterface);
|
||||
|
||||
List<CreateDeviceRequest.InterfaceAddressConfigRequest> addressConfigs = i.getAddressConfigs();
|
||||
|
||||
if (addressConfigs == null || addressConfigs.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
addressConfigs.forEach(a -> {
|
||||
InterfaceAddressConfig iac = new InterfaceAddressConfig();
|
||||
iac.setInterfaceId(networkInterface.getId());
|
||||
iac.setVlanId(a.getVlanId());
|
||||
iac.setIpAddress(a.getIpAddress());
|
||||
iac.setGatewayIp(a.getGatewayIp());
|
||||
iac.setSubnetMask(a.getSubnetMask());
|
||||
iac.setBroadcastAddress(a.getBroadcastAddress());
|
||||
iac.setIsPrimary(a.getIsPrimary());
|
||||
iac.setIsDhcp(a.getIsDhcp());
|
||||
iac.setMtu(a.getMtu());
|
||||
interfaceAddressConfigMapper.insert(iac);
|
||||
|
||||
List<String> dnsServers = a.getDnsServers();
|
||||
if (dnsServers == null || dnsServers.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
List<Long> dnsServerIdListByNameList = dnsServerService.getDnsServerIdListByNameList(dnsServers);
|
||||
for (int j = 0; j < dnsServerIdListByNameList.size(); j++) {
|
||||
InterfaceDnsMapping mapping = new InterfaceDnsMapping();
|
||||
mapping.setConfigId(iac.getId());
|
||||
mapping.setDnsServerId(dnsServerIdListByNameList.get(j));
|
||||
mapping.setPriority(j);
|
||||
interfaceDnsMappingMapper.insert(mapping);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
if (rows > 0){
|
||||
return new DeviceResponse(device);
|
||||
}else {
|
||||
throw new BizException("添加设备失败");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeviceResponse getDeviceById(Long id) {
|
||||
|
||||
Device device = this.baseMapper.selectOneWithTypeById(id);
|
||||
if (device == null){
|
||||
throw new BizException(ErrorCode.DEVICE_NOT_FOUND);
|
||||
}
|
||||
return new DeviceResponse(device);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void deleteDevice(Long id) {
|
||||
int i = this.baseMapper.deleteDevice(id);
|
||||
if (i <= 0){
|
||||
throw new BizException(ErrorCode.DEVICE_DELETE_FAILED);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void deleteDeviceReal(Long id) {
|
||||
log.info("删除设备id:{}",id);
|
||||
int i = this.baseMapper.deleteById(id);
|
||||
if (i <= 0){
|
||||
throw new BizException(ErrorCode.DEVICE_DELETE_FAILED);
|
||||
}
|
||||
log.info("删除设备成功");
|
||||
}
|
||||
|
||||
public IPage<DeviceResponse> getAllDevices(DeviceQueryRequest request) {
|
||||
|
||||
|
||||
if (request == null) {
|
||||
request = new DeviceQueryRequest();
|
||||
}
|
||||
|
||||
long current = Objects.requireNonNullElse(request.getPageNum(), 1);
|
||||
long size = Objects.requireNonNullElse(request.getPageSize(), 10);
|
||||
|
||||
|
||||
Page<Device> page = new Page<>(current, size);
|
||||
|
||||
|
||||
LambdaQueryWrapper<Device> wrapper = new LambdaQueryWrapper<>();
|
||||
|
||||
wrapper.eq(request.getType() != null && request.getType() > 0, Device::getTypeId, request.getType());
|
||||
|
||||
if (StringUtils.hasText(request.getKeyword())) {
|
||||
|
||||
String keyword = request.getKeyword();
|
||||
wrapper.and(w -> w
|
||||
.like(Device::getName, keyword)
|
||||
.or()
|
||||
.like(Device::getModel, keyword)
|
||||
.or()
|
||||
.like(Device::getManufacturer, keyword)
|
||||
.or()
|
||||
.like(Device::getRemark, keyword)
|
||||
.or()
|
||||
.like(Device::getSnmpCommunity, keyword)
|
||||
);
|
||||
}
|
||||
|
||||
IPage<Device> deviceIPage = this.baseMapper.selectPage(page, wrapper);
|
||||
return deviceIPage.convert(DeviceResponse::new);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeviceResponse updateDevice(UpdateDeviceRequest updateDeviceRequest) {
|
||||
|
||||
Device device = new Device();
|
||||
//判断各字段是否为空 不为空再更新
|
||||
if (updateDeviceRequest.getId() != null){
|
||||
device.setId(updateDeviceRequest.getId());
|
||||
}
|
||||
if (updateDeviceRequest.getName() != null){
|
||||
device.setName(updateDeviceRequest.getName());
|
||||
}
|
||||
if (updateDeviceRequest.getModel() != null){
|
||||
device.setModel(updateDeviceRequest.getModel());
|
||||
}
|
||||
if (updateDeviceRequest.getTypeId() != null){
|
||||
device.setTypeId(updateDeviceRequest.getTypeId());
|
||||
}
|
||||
if (updateDeviceRequest.getLocationId() != null){
|
||||
device.setLocationId(updateDeviceRequest.getLocationId());
|
||||
}
|
||||
if (updateDeviceRequest.getSnmpCommunity() != null){
|
||||
device.setSnmpCommunity(updateDeviceRequest.getSnmpCommunity());
|
||||
}
|
||||
if (updateDeviceRequest.getManufacturer() != null){
|
||||
device.setManufacturer(updateDeviceRequest.getManufacturer());
|
||||
}
|
||||
if (updateDeviceRequest.getPurchaseDate() != null){
|
||||
device.setPurchaseDate(updateDeviceRequest.getPurchaseDate());
|
||||
}
|
||||
if (updateDeviceRequest.getStatus() >= 0 && updateDeviceRequest.getStatus() <= 2){
|
||||
device.setStatus(updateDeviceRequest.getStatus());
|
||||
}
|
||||
if (updateDeviceRequest.getRemark() != null){
|
||||
device.setRemark(updateDeviceRequest.getRemark());
|
||||
}
|
||||
|
||||
int rows = this.baseMapper.updateById(device);
|
||||
if (rows > 0){
|
||||
return new DeviceResponse(device);
|
||||
}else {
|
||||
throw new BizException("更新设备失败");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package cn.nopj.chaos_api.service.impl;
|
||||
|
||||
import cn.nopj.chaos_api.common.constants.ErrorCode;
|
||||
import cn.nopj.chaos_api.common.exceotion.BizException;
|
||||
import cn.nopj.chaos_api.domain.entity.DeviceType;
|
||||
import cn.nopj.chaos_api.dto.response.DeviceTypeResponse;
|
||||
import cn.nopj.chaos_api.dto.response.OptionResponse;
|
||||
import cn.nopj.chaos_api.mapper.DriveTypeMapper;
|
||||
import cn.nopj.chaos_api.service.DeviceTypeService;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class DeviceTypeServiceImpl extends ServiceImpl<DriveTypeMapper, DeviceType> implements DeviceTypeService {
|
||||
|
||||
@Override
|
||||
public List<DeviceTypeResponse> getAllDeviceTypes() {
|
||||
List<DeviceType> deviceTypes = this.baseMapper.selectList(null);
|
||||
if (deviceTypes == null || deviceTypes.isEmpty()){
|
||||
throw new BizException(ErrorCode.DEVICE_TYPE_NOT_FOUND);
|
||||
}
|
||||
return deviceTypes.stream().map(DeviceTypeResponse::new).toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<OptionResponse> getDeviceTypeOptions() {
|
||||
return this.lambdaQuery()
|
||||
.select(DeviceType::getId, DeviceType::getName)
|
||||
.list()
|
||||
.stream()
|
||||
.map(deviceType -> new OptionResponse(deviceType.getName(), deviceType.getId().toString()))
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package cn.nopj.chaos_api.service.impl;
|
||||
|
||||
import cn.nopj.chaos_api.common.constants.ErrorCode;
|
||||
import cn.nopj.chaos_api.common.exceotion.BizException;
|
||||
import cn.nopj.chaos_api.domain.entity.DnsServer;
|
||||
import cn.nopj.chaos_api.mapper.DnsServerMapper;
|
||||
import cn.nopj.chaos_api.service.DnsServerService;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Service
|
||||
public class DnsServerServiceImpl extends ServiceImpl<DnsServerMapper, DnsServer> implements DnsServerService {
|
||||
|
||||
@Override
|
||||
public Long getDnsServerIdByName(String dnsAddress) {
|
||||
|
||||
if (dnsAddress == null || dnsAddress.isEmpty()){
|
||||
throw new BizException(ErrorCode.DNS_NOT_FOUND);
|
||||
}
|
||||
DnsServer dnsServer = this.baseMapper.selectOne(new LambdaQueryWrapper<DnsServer>().eq(DnsServer::getName, dnsAddress));
|
||||
|
||||
if (dnsServer == null ){
|
||||
throw new BizException(ErrorCode.DNS_NOT_DEFINED);
|
||||
}
|
||||
|
||||
return dnsServer.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Long> getDnsServerIdListByNameList(List<String> nameList) {
|
||||
|
||||
List<DnsServer> dnsServers = this.baseMapper.selectList(null);
|
||||
|
||||
if (dnsServers == null || dnsServers.isEmpty() ){
|
||||
throw new BizException(ErrorCode.DB_DNS_NOT_FOUND);
|
||||
}
|
||||
|
||||
Map<String, Long> map = new HashMap<>();
|
||||
for (DnsServer dnsServer : dnsServers) {
|
||||
map.put(dnsServer.getName(), dnsServer.getId());
|
||||
}
|
||||
|
||||
return nameList.stream().map(map::get).toList();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package cn.nopj.chaos_api.service.impl;
|
||||
|
||||
import cn.nopj.chaos_api.dto.response.FileUploadResponse;
|
||||
import cn.nopj.chaos_api.service.ImageService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class ImageServiceImpl implements ImageService {
|
||||
@Override
|
||||
public FileUploadResponse uploadImage(String fileName, InputStream content) {
|
||||
//todo 完成上传图片功能
|
||||
return new FileUploadResponse();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package cn.nopj.chaos_api.service.impl;
|
||||
|
||||
import cn.nopj.chaos_api.domain.entity.Role;
|
||||
import cn.nopj.chaos_api.dto.response.OptionResponse;
|
||||
import cn.nopj.chaos_api.mapper.RoleMapper;
|
||||
import cn.nopj.chaos_api.service.RoleService;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements RoleService {
|
||||
@Override
|
||||
public List<OptionResponse> getAllRoles() {
|
||||
|
||||
return this.lambdaQuery()
|
||||
.select(Role::getId, Role::getName)
|
||||
.list()
|
||||
.stream()
|
||||
.map(role -> new OptionResponse(role.getName(), role.getId().toString()))
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
package cn.nopj.chaos_api.service.impl;
|
||||
|
||||
import cn.nopj.chaos_api.common.constants.ErrorCode;
|
||||
import cn.nopj.chaos_api.common.exceotion.BizException;
|
||||
import cn.nopj.chaos_api.domain.entity.User;
|
||||
import cn.nopj.chaos_api.dto.request.UserProfileUpdateRequest;
|
||||
import cn.nopj.chaos_api.dto.response.UserProfileResponse;
|
||||
import cn.nopj.chaos_api.mapper.UserMapper;
|
||||
import cn.nopj.chaos_api.service.UserProfileService;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class UserProfileServiceImpl implements UserProfileService {
|
||||
@Autowired
|
||||
private PasswordEncoder passwordEncoder;
|
||||
@Autowired
|
||||
UserMapper userMapper;
|
||||
@Override
|
||||
public void setUserPassword(Long Id, String password) {
|
||||
|
||||
log.info("重置用户id: {}的密码", Id);
|
||||
User user = userMapper.selectById(Id);
|
||||
//查询用户是否存在
|
||||
|
||||
if (user == null) {
|
||||
throw new BizException(ErrorCode.USER_NOT_EXISTS);
|
||||
}
|
||||
user.setPassword(passwordEncoder.encode(password));
|
||||
int i = userMapper.updateById(user);
|
||||
log.info("重置用户id: {}的密码结果: {}", Id, i);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UserProfileResponse> getAllUsers() {
|
||||
List<User> allUserWithRoles = userMapper.findAllUserWithRoles();
|
||||
|
||||
if (allUserWithRoles == null){
|
||||
throw new RuntimeException("获取所有用户信息失败");
|
||||
}
|
||||
|
||||
return allUserWithRoles.stream().map(UserProfileResponse::new).toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUserPassword(String userId, String password) {
|
||||
try{
|
||||
Long Id = Long.parseLong(userId);
|
||||
setUserPassword(Id, password);
|
||||
}catch (NumberFormatException e) {
|
||||
throw new BizException(ErrorCode.USER_ID_INVALID);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateUsername(String username, String newUsername) {
|
||||
if (newUsername == null || newUsername.isEmpty()){
|
||||
throw new BizException(ErrorCode.USER_UPDATE_USERNAME_FAILED);
|
||||
}
|
||||
User user = userMapper.selectByUsername(username);
|
||||
if (user == null) {
|
||||
throw new BizException(ErrorCode.USER_NOT_EXISTS);
|
||||
}
|
||||
user.setUsername(newUsername);
|
||||
int i = userMapper.updateById(user);
|
||||
log.info("更新用户名结果: {}", i);
|
||||
if (i == 0) {
|
||||
throw new BizException(ErrorCode.USER_UPDATE_USERNAME_FAILED);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserProfileResponse findUserWithRolesByUsername(String username) {
|
||||
if (username == null || username.isEmpty()){
|
||||
throw new BizException(ErrorCode.USER_NOT_EXISTS);
|
||||
}
|
||||
log.info("查询用户名: {}", username);
|
||||
|
||||
User user = userMapper.findUserWithRolesByUsername(username);
|
||||
|
||||
log.info("查询用户名: {} 结果: {}", username, user);
|
||||
if (user == null) {
|
||||
throw new BizException(ErrorCode.USER_NOT_EXISTS);
|
||||
}
|
||||
return new UserProfileResponse(user);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserProfileResponse setUserNickname(String username, String nickname) {
|
||||
|
||||
User user = userMapper.selectByUsername(username);
|
||||
if (user == null) {
|
||||
throw new BizException(ErrorCode.USER_NOT_EXISTS);
|
||||
}
|
||||
user.setNickname(nickname);
|
||||
int i = userMapper.updateById(user);
|
||||
|
||||
log.info("更新用户昵称结果: {}", i);
|
||||
if (i == 0) {
|
||||
throw new BizException(ErrorCode.USER_UPDATE_NICKNAME_FAILED);
|
||||
}
|
||||
return new UserProfileResponse(user);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserProfileResponse setUserNickname(Long userId, String nickname) {
|
||||
User user = userMapper.selectById(userId);
|
||||
if (user == null) {
|
||||
throw new BizException(ErrorCode.USER_NOT_EXISTS);
|
||||
}
|
||||
user.setNickname(nickname);
|
||||
int i = userMapper.updateById(user);
|
||||
log.info("更新用户昵称结果: {}", i);
|
||||
if (i == 0) {
|
||||
throw new BizException(ErrorCode.USER_UPDATE_NICKNAME_FAILED);
|
||||
}
|
||||
return new UserProfileResponse(user);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public UserProfileResponse updateUserProfile(String username, UserProfileUpdateRequest request) {
|
||||
|
||||
User user = userMapper.findUserWithRolesByUsername(username);
|
||||
if (user == null) {
|
||||
throw new BizException(ErrorCode.USER_NOT_EXISTS);
|
||||
}
|
||||
|
||||
if (request.getNickname() != null){
|
||||
user.setNickname(request.getNickname());
|
||||
}
|
||||
if (request.getPassword() != null){
|
||||
user.setPassword(passwordEncoder.encode(request.getPassword()));
|
||||
}
|
||||
|
||||
int i = userMapper.updateById(user);
|
||||
|
||||
log.info("更新用户信息结果: {}", i);
|
||||
if (i == 0) {
|
||||
throw new BizException(ErrorCode.USER_UPDATE_PROFILE_FAILED);
|
||||
}
|
||||
return new UserProfileResponse(user);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IPage<UserProfileResponse> getAllUsers(Integer pageNum, Integer pageSize, String keyword, Long roleId) {
|
||||
Page<User> page = new Page<>(pageNum, pageSize);
|
||||
IPage<User> userPage = userMapper.selectPageWithRoles(page, keyword,roleId);
|
||||
return userPage.convert(UserProfileResponse::new);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserProfileResponse findUserWithRolesById(Long id) {
|
||||
|
||||
if (id == null){
|
||||
throw new BizException(ErrorCode.USER_ID_INVALID);
|
||||
}
|
||||
|
||||
User userWithRolesById = userMapper.findUserWithRolesById(id);
|
||||
|
||||
if (userWithRolesById == null){
|
||||
throw new BizException(ErrorCode.USER_NOT_EXISTS);
|
||||
}
|
||||
|
||||
return new UserProfileResponse(userWithRolesById);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
package cn.nopj.chaos_api.service.impl;
|
||||
|
||||
import cn.nopj.chaos_api.common.constants.ErrorCode;
|
||||
import cn.nopj.chaos_api.common.exceotion.BizException;
|
||||
import cn.nopj.chaos_api.domain.entity.UserRole;
|
||||
import cn.nopj.chaos_api.dto.request.SetUserRoleRequest;
|
||||
import cn.nopj.chaos_api.mapper.UserRoleMapper;
|
||||
import cn.nopj.chaos_api.service.UserRoleService;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.dao.DataIntegrityViolationException;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
// 继承 ServiceImpl<UserRoleMapper, UserRole> 以获得批量操作能力
|
||||
public class UserRoleServiceImpl extends ServiceImpl<UserRoleMapper, UserRole> implements UserRoleService {
|
||||
|
||||
/**
|
||||
* 批量分配角色给用户。
|
||||
*/
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@Override
|
||||
public void assignRolesToUser(SetUserRoleRequest request) {
|
||||
if (request == null || request.getUserId() == null || request.getUserId() <= 0L){
|
||||
throw new BizException(ErrorCode.USER_ID_INVALID);
|
||||
}
|
||||
Long userId = request.getUserId();
|
||||
|
||||
if (request.getRolesId() == null || request.getRolesId().isEmpty()){
|
||||
this.baseMapper.delete(new LambdaQueryWrapper<UserRole>().eq(UserRole::getUserId,userId));
|
||||
return;
|
||||
}
|
||||
|
||||
List<Long> rolesId = request.getRolesId();
|
||||
|
||||
// 1. 数据预校验:确保没有无效角色ID (0或null)
|
||||
if (rolesId.stream().anyMatch(roleId -> roleId == null || roleId <= 0L)) {
|
||||
throw new BizException(ErrorCode.ROLE_ID_INVALID);
|
||||
}
|
||||
|
||||
// 2. 构建批量插入列表
|
||||
List<UserRole> userRoleList = rolesId.stream()
|
||||
.map(roleId -> {
|
||||
var userRole = new UserRole();
|
||||
userRole.setUserId(userId);
|
||||
userRole.setRoleId(roleId);
|
||||
return userRole;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
|
||||
try {
|
||||
|
||||
// 替代:先删除用户所有旧角色,再插入新的角色集合 (常用授权模式)
|
||||
this.remove(new LambdaQueryWrapper<UserRole>().eq(UserRole::getUserId, userId));
|
||||
|
||||
// 保持原有逻辑:只添加新的角色
|
||||
boolean success = this.saveBatch(userRoleList);
|
||||
|
||||
if (!success) {
|
||||
// saveBatch 返回 false,通常是 MyBatis-Plus 内部错误或部分失败
|
||||
throw new BizException("批量分配角色操作失败。");
|
||||
}
|
||||
} catch (DataIntegrityViolationException e) {
|
||||
// 捕获数据完整性约束失败 (如外键约束失败: role_id 不存在)
|
||||
log.error("分配角色时外键约束失败: userId={}, rolesId={}", userId, rolesId, e);
|
||||
|
||||
// 给出更具体的提示,可能需要检查是哪个 role_id 不存在
|
||||
throw new BizException("分配角色失败:列表中包含一个或多个不存在的角色ID。");
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量解除用户的角色关联。
|
||||
* 优化点: 使用 MyBatis-Plus 的批量删除,而不是在循环中单次删除。
|
||||
* @param request 包含用户ID和角色ID列表
|
||||
* @return 实际解除关联的记录数
|
||||
*/
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@Override
|
||||
public int revokeRolesFromUser(SetUserRoleRequest request) {
|
||||
if (request == null || request.getUserId() == null || request.getUserId() <= 0L){
|
||||
throw new BizException(ErrorCode.USER_ID_INVALID);
|
||||
}
|
||||
if (request.getRolesId() == null || request.getRolesId().isEmpty()){
|
||||
throw new BizException(ErrorCode.ROLE_ID_INVALID);
|
||||
}
|
||||
|
||||
Long userId = request.getUserId();
|
||||
List<Long> rolesId = request.getRolesId();
|
||||
|
||||
|
||||
LambdaQueryWrapper<UserRole> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(UserRole::getUserId, userId)
|
||||
.in(UserRole::getRoleId, rolesId); // 批量删除指定的角色ID
|
||||
|
||||
|
||||
int count = this.baseMapper.delete(wrapper);
|
||||
|
||||
if (count == 0 && !rolesId.isEmpty()){
|
||||
// 如果用户传入了角色ID列表但删除了 0 行,可能是这些角色未分配给该用户
|
||||
log.warn("用户 {} 解除角色 {} 失败,0条记录被删除。", userId, rolesId);
|
||||
throw new BizException("解除角色失败,用户可能未分配这些角色。");
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
}
|
||||
@@ -44,7 +44,6 @@
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
@@ -69,7 +68,6 @@
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package cn.nopj.chaos_api;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication(scanBasePackages = "cn.nopj.chaos_api")
|
||||
@Slf4j
|
||||
public class ChaosApiWebApplication {
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(ChaosApiWebApplication.class, args);
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
package cn.nopj.chaos_api.config;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||
|
||||
@Configuration
|
||||
@EnableTransactionManagement
|
||||
public class AppConfig {
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package cn.nopj.chaos_api.config;
|
||||
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.DbType;
|
||||
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
|
||||
import org.mybatis.spring.annotation.MapperScan;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
@MapperScan("cn.nopj.chaos_api.mapper")
|
||||
public class MybatisPlusConfig {
|
||||
|
||||
@Bean
|
||||
public MybatisPlusInterceptor mybatisPlusInterceptor() {
|
||||
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
|
||||
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MARIADB));
|
||||
return interceptor;
|
||||
}
|
||||
}
|
||||
@@ -47,13 +47,13 @@ public class SecurityConfig {
|
||||
return authenticationConfiguration.getAuthenticationManager();
|
||||
}
|
||||
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
||||
.authorizeHttpRequests(auth -> auth
|
||||
// 允许所有对 /api/public/** 的匿名访问
|
||||
.requestMatchers("/api/auth/login","/api/auth/register").permitAll()
|
||||
.requestMatchers("/api/public/*","/api/auth/login","/api/auth/register").permitAll()
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
// 禁用 CSRF,因为现代前后端分离项目通常使用 Token
|
||||
@@ -75,8 +75,7 @@ public class SecurityConfig {
|
||||
|
||||
CorsConfiguration configuration = new CorsConfiguration();
|
||||
configuration.setAllowedOrigins(Arrays.asList(
|
||||
"http://localhost:63342", // 你的前端运行的地址
|
||||
"http://localhost:8080", // 其他可能的前端地址
|
||||
"http://localhost:5173", // 其他可能的前端地址
|
||||
"http://127.0.0.1:5500", // 另一个常见的本地开发地址
|
||||
"null" // 如果是从文件系统直接打开HTML文件,Origin 会是 "null"。仅用于开发!
|
||||
));
|
||||
|
||||
@@ -9,6 +9,7 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
@@ -17,6 +18,7 @@ import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* JWT 登录授权过滤器
|
||||
@@ -25,8 +27,6 @@ import java.io.IOException;
|
||||
@Slf4j
|
||||
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
|
||||
|
||||
@Autowired
|
||||
private UserDetailsService userDetailsService;
|
||||
@Autowired
|
||||
private JwtTokenUtil jwtTokenUtil;
|
||||
@Value("${jwt.tokenHeader}")
|
||||
@@ -38,10 +38,8 @@ public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
|
||||
protected void doFilterInternal(HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
FilterChain chain) throws ServletException, IOException {
|
||||
log.info("JWT 登录授权过滤器");
|
||||
|
||||
String authHeader = request.getHeader(this.tokenHeader);
|
||||
log.info("authHeader: {}", authHeader);
|
||||
if (authHeader != null && authHeader.startsWith(this.tokenHead)) {
|
||||
String authToken = authHeader.substring(this.tokenHead.length()+1);
|
||||
log.info("authToken={}", authToken);
|
||||
@@ -49,11 +47,19 @@ public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
|
||||
log.info("username={}", username);
|
||||
// 如果 Token 中有用户名但上下文中没有,说明是首次登录
|
||||
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
|
||||
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
|
||||
|
||||
// 验证 Token 是否有效
|
||||
if (jwtTokenUtil.validateToken(authToken, userDetails)) {
|
||||
if (jwtTokenUtil.validateToken(authToken)) {
|
||||
|
||||
List<String> authorityStrings = jwtTokenUtil.getAuthoritiesFromToken(authToken);
|
||||
|
||||
List<SimpleGrantedAuthority> authorities = authorityStrings.stream()
|
||||
.map(SimpleGrantedAuthority::new)
|
||||
.toList();
|
||||
log.info("authorities: {}", authorities);
|
||||
UsernamePasswordAuthenticationToken authentication =
|
||||
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
|
||||
new UsernamePasswordAuthenticationToken(username, null, authorities);
|
||||
request.setAttribute("currentUsername", username);
|
||||
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
|
||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint {
|
||||
response.setContentType("application/json");
|
||||
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
|
||||
|
||||
ApiResult<Object> result = ApiResult.failed("未授权");
|
||||
ApiResult<Object> result = ApiResult.failed(403,"权限不足");
|
||||
|
||||
String string = JSONObject.toJSONString(result);
|
||||
response.getWriter().print(string);
|
||||
|
||||
@@ -1,61 +1,50 @@
|
||||
package cn.nopj.chaos_api.controller;
|
||||
|
||||
import cn.nopj.chaos_api.dto.LoginRequest;
|
||||
import cn.nopj.chaos_api.dto.RegisterRequest;
|
||||
import cn.nopj.chaos_api.converter.UserConverter;
|
||||
import cn.nopj.chaos_api.dto.request.AuthLoginRequest;
|
||||
import cn.nopj.chaos_api.dto.request.AuthRegisterRequest;
|
||||
import cn.nopj.chaos_api.dto.response.AuthTokenResponse;
|
||||
import cn.nopj.chaos_api.model.ApiResult;
|
||||
import cn.nopj.chaos_api.service.AuthService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.security.authentication.BadCredentialsException;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 认证管理
|
||||
*
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/auth")
|
||||
public class AuthController {
|
||||
|
||||
@Autowired
|
||||
private AuthService authService;
|
||||
@Value("${jwt.tokenHead}")
|
||||
private String tokenHead;
|
||||
|
||||
@Autowired
|
||||
private UserConverter userConverter;
|
||||
/**
|
||||
* 注册
|
||||
*
|
||||
* @param registerRequest 注册信息
|
||||
* @param authRegisterRequest 注册信息
|
||||
* @return 注册结果
|
||||
*/
|
||||
@PostMapping("/register")
|
||||
public ApiResult<?> register(@RequestBody RegisterRequest registerRequest) {
|
||||
if (authService.register(registerRequest) != null) {
|
||||
return ApiResult.success("注册成功");
|
||||
}
|
||||
return ApiResult.failed("用户名已存在");
|
||||
public ApiResult<?> register(@RequestBody @Validated AuthRegisterRequest authRegisterRequest) {
|
||||
return ApiResult.success(authService.register(userConverter.convert(authRegisterRequest)));
|
||||
}
|
||||
/**
|
||||
* 登录
|
||||
*
|
||||
* @param loginRequest 登录信息
|
||||
* @param authLoginRequest 登录信息
|
||||
* @return 登录结果
|
||||
*/
|
||||
@PostMapping("/login")
|
||||
public ApiResult<?> login(@RequestBody LoginRequest loginRequest) {
|
||||
try {
|
||||
String token = authService.login(loginRequest.getUsername(), loginRequest.getPassword());
|
||||
|
||||
Map<String, String> tokenMap = new HashMap<>();
|
||||
tokenMap.put("token", token);
|
||||
tokenMap.put("tokenHead", tokenHead);
|
||||
return ApiResult.success(tokenMap);
|
||||
|
||||
}catch (BadCredentialsException e){
|
||||
return ApiResult.failed(e.getMessage());
|
||||
}
|
||||
|
||||
public ApiResult<AuthTokenResponse> login(@RequestBody @Validated AuthLoginRequest authLoginRequest) {
|
||||
return ApiResult.success(authService.login(authLoginRequest.getUsername(), authLoginRequest.getPassword()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
package cn.nopj.chaos_api.controller;
|
||||
|
||||
|
||||
import cn.nopj.chaos_api.dto.request.CreateDeviceRequest;
|
||||
import cn.nopj.chaos_api.dto.request.DeviceQueryRequest;
|
||||
import cn.nopj.chaos_api.dto.request.UpdateDeviceRequest;
|
||||
import cn.nopj.chaos_api.dto.response.DeviceResponse;
|
||||
import cn.nopj.chaos_api.model.ApiResult;
|
||||
import cn.nopj.chaos_api.service.DeviceService;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
|
||||
/**
|
||||
* 设备管理
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/api/devices")
|
||||
public class DeviceController {
|
||||
|
||||
private final DeviceService deviceService;
|
||||
|
||||
public DeviceController(DeviceService deviceService) {
|
||||
this.deviceService = deviceService;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 查询所有设备信息
|
||||
* @return 所有设备信息
|
||||
*/
|
||||
@GetMapping
|
||||
public ApiResult<IPage<DeviceResponse>> getAllDevices(DeviceQueryRequest request){
|
||||
return ApiResult.success(deviceService.getAllDevices(request));
|
||||
}
|
||||
/**
|
||||
* 新建设备信息
|
||||
* @param createDeviceRequest 设备信息
|
||||
* @return 新建设备信息结果
|
||||
*/
|
||||
@PostMapping
|
||||
public ApiResult<DeviceResponse> createDevice(@RequestBody @Validated CreateDeviceRequest createDeviceRequest){
|
||||
log.info("创建设备信息: {}", createDeviceRequest);
|
||||
return ApiResult.success(deviceService.createDevice(createDeviceRequest));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询指定设备信息
|
||||
* @return 指定设备信息
|
||||
*/
|
||||
@GetMapping("/{id}")
|
||||
public ApiResult<DeviceResponse> getDeviceById(@PathVariable("id") Long id){
|
||||
return ApiResult.success(deviceService.getDeviceById(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除指定设备信息
|
||||
* @return 逻辑删除指定设备信息结果
|
||||
*/
|
||||
@DeleteMapping("/{id}")
|
||||
public ApiResult<String> deleteDevice(@PathVariable("id") Long id){
|
||||
deviceService.deleteDevice(id);
|
||||
return ApiResult.success("删除成功");
|
||||
}
|
||||
/**
|
||||
* 修改指定设备信息
|
||||
* @return 修改指定设备信息结果
|
||||
*/
|
||||
@PutMapping({"/{id}"})
|
||||
public ApiResult<DeviceResponse> updateDevice(@RequestBody @Validated UpdateDeviceRequest updateDeviceRequest){
|
||||
return ApiResult.success(deviceService.updateDevice(updateDeviceRequest));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package cn.nopj.chaos_api.controller;
|
||||
|
||||
import cn.nopj.chaos_api.dto.response.DeviceTypeResponse;
|
||||
import cn.nopj.chaos_api.dto.response.OptionResponse;
|
||||
import cn.nopj.chaos_api.model.ApiResult;
|
||||
import cn.nopj.chaos_api.service.DeviceTypeService;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 设备类型管理
|
||||
*
|
||||
* @author nopj
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/device-types")
|
||||
public class DeviceTypeController {
|
||||
private final DeviceTypeService deviceTypeService;
|
||||
|
||||
public DeviceTypeController(DeviceTypeService deviceTypeService) {
|
||||
this.deviceTypeService = deviceTypeService;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有设备类型
|
||||
*
|
||||
* @return 所有设备类型
|
||||
*/
|
||||
@GetMapping
|
||||
public ApiResult<List<DeviceTypeResponse>> getAllDeviceTypes() {
|
||||
return ApiResult.success(deviceTypeService.getAllDeviceTypes());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有设备类型选项
|
||||
* @return 所有设备类型选项
|
||||
*/
|
||||
@GetMapping("/options")
|
||||
public ApiResult<List<OptionResponse>> getDeviceTypeOptions() {
|
||||
return ApiResult.success(deviceTypeService.getDeviceTypeOptions());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package cn.nopj.chaos_api.controller;
|
||||
|
||||
import cn.nopj.chaos_api.dto.response.FileUploadResponse;
|
||||
import cn.nopj.chaos_api.model.ApiResult;
|
||||
import cn.nopj.chaos_api.service.ImageService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* 图片管理
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/api/image")
|
||||
public class ImageController {
|
||||
@Autowired
|
||||
private ImageService imageService;
|
||||
|
||||
/**
|
||||
* 上传图片
|
||||
* @param file 文件
|
||||
* @return 上传结果
|
||||
*/
|
||||
@RequestMapping("/upload")
|
||||
ApiResult<String> uploadImage(@RequestParam("file") MultipartFile file) {
|
||||
if (file.isEmpty()){
|
||||
return ApiResult.failed("上传文件不能为空");
|
||||
}
|
||||
try {
|
||||
String fileName = file.getOriginalFilename();
|
||||
InputStream inputStream = file.getInputStream();
|
||||
FileUploadResponse response = imageService.uploadImage(fileName, inputStream);
|
||||
return ApiResult.success(response.getFileDownloadUri());
|
||||
} catch (IOException e) {
|
||||
log.error("上传文件失败", e);
|
||||
return ApiResult.failed("上传文件失败");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package cn.nopj.chaos_api.controller;
|
||||
|
||||
|
||||
import cn.nopj.chaos_api.dto.request.SetUserRoleRequest;
|
||||
import cn.nopj.chaos_api.dto.response.OptionResponse;
|
||||
import cn.nopj.chaos_api.model.ApiResult;
|
||||
import cn.nopj.chaos_api.service.RoleService;
|
||||
import cn.nopj.chaos_api.service.UserRoleService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
||||
/**
|
||||
* 角色管理
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/api/roles")
|
||||
public class RoleController {
|
||||
|
||||
@Autowired
|
||||
private UserRoleService userRoleService;
|
||||
|
||||
@Autowired
|
||||
private RoleService roleService;
|
||||
|
||||
|
||||
/**
|
||||
* 获取用户角色选项
|
||||
*/
|
||||
@GetMapping("/options")
|
||||
public ApiResult<List<OptionResponse>> getUserRole() {
|
||||
|
||||
return ApiResult.success(roleService.getAllRoles());
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置用户角色
|
||||
* @param request 请求参数
|
||||
* @return 处理结果
|
||||
*/
|
||||
@PreAuthorize("hasAuthority('admin')")
|
||||
@PostMapping("/users")
|
||||
public ApiResult<String> assignRolesToUser(@RequestBody @Validated SetUserRoleRequest request) {
|
||||
userRoleService.assignRolesToUser(request);
|
||||
return ApiResult.success("用户角色设置成功");
|
||||
}
|
||||
/**
|
||||
* 取消用户角色
|
||||
* @param request 请求参数
|
||||
* @return 处理结果
|
||||
*/
|
||||
@PreAuthorize("hasAuthority('admin')")
|
||||
@DeleteMapping("/users")
|
||||
public ApiResult<?> revokeRolesFromUser(@RequestBody @Validated SetUserRoleRequest request) {
|
||||
return ApiResult.success("用户角色取消成功",userRoleService.revokeRolesFromUser(request));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
package cn.nopj.chaos_api.controller;
|
||||
|
||||
|
||||
import cn.nopj.chaos_api.dto.request.SetUserNicknameRequest;
|
||||
import cn.nopj.chaos_api.dto.request.SetUserPasswordRequest;
|
||||
import cn.nopj.chaos_api.dto.request.UpdateUsernameRequest;
|
||||
import cn.nopj.chaos_api.dto.request.UserProfileUpdateRequest;
|
||||
import cn.nopj.chaos_api.dto.response.UserProfileResponse;
|
||||
import cn.nopj.chaos_api.model.ApiResult;
|
||||
import cn.nopj.chaos_api.service.UserProfileService;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* 用户管理
|
||||
*
|
||||
* @author nopj
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/api/users")
|
||||
@RequiredArgsConstructor
|
||||
public class UserController {
|
||||
|
||||
private final UserProfileService userProfileService;
|
||||
/**
|
||||
* 获取用户列表 (分页)
|
||||
* URL: GET /api/users?pageNum=1&pageSize=10
|
||||
*/
|
||||
@PreAuthorize("hasAuthority('admin')")
|
||||
@GetMapping
|
||||
public ApiResult<IPage<UserProfileResponse>> listUsers(
|
||||
@RequestParam(defaultValue = "1", name = "pageNum") Integer pageNum,
|
||||
@RequestParam(defaultValue = "10", name = "pageSize") Integer pageSize,
|
||||
@RequestParam(required = false,name = "keyword") String keyword,
|
||||
@RequestParam(required = false,name = "roleId") Long roleId
|
||||
) {
|
||||
log.info("获取用户列表 (分页): pageNum={}, pageSize={}, keyword={}, roleId={}" , pageNum, pageSize, keyword, roleId);
|
||||
return ApiResult.success(userProfileService.getAllUsers(pageNum, pageSize, keyword, roleId));
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public ApiResult<UserProfileResponse> getUserProfile(@PathVariable Long id) {
|
||||
return ApiResult.success(userProfileService.findUserWithRolesById(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 管理员重置指定用户的密码
|
||||
* URL: PUT /api/users/{userId}/password
|
||||
*/
|
||||
@PreAuthorize("hasAuthority('admin')")
|
||||
@PutMapping("/{userId}/password")
|
||||
public ApiResult<String> resetUserPassword(
|
||||
@PathVariable Long userId,
|
||||
@RequestBody @Validated SetUserPasswordRequest request) {
|
||||
userProfileService.setUserPassword(userId, request.getPassword());
|
||||
return ApiResult.success("用户密码修改成功");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取当前登录用户信息
|
||||
* URL: GET /api/users/me (或 /api/users/profile)
|
||||
*/
|
||||
@GetMapping("/me")
|
||||
public ApiResult<UserProfileResponse> getCurrentUserProfile(@RequestAttribute("currentUsername") String username) {
|
||||
return ApiResult.success(userProfileService.findUserWithRolesByUsername(username));
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新当前用户的基础信息
|
||||
* URL: PATCH /api/users/me
|
||||
*/
|
||||
@PatchMapping("/me")
|
||||
public ApiResult<UserProfileResponse> updateMyProfile(
|
||||
@RequestAttribute("currentUsername") String username,
|
||||
@RequestBody @Validated UserProfileUpdateRequest request) {
|
||||
return ApiResult.success(userProfileService.updateUserProfile(username, request));
|
||||
}
|
||||
/**
|
||||
* 更新当前用户的用户名
|
||||
* URL: PUT /api/users/me/username
|
||||
* @deprecated 修改用户名会导致 Token 失效
|
||||
*/
|
||||
@Deprecated
|
||||
@PutMapping("/me/username")
|
||||
public ApiResult<String> updateMyUsername(
|
||||
@RequestAttribute("currentUsername") String username,
|
||||
@RequestBody @Validated UpdateUsernameRequest request) {
|
||||
userProfileService.updateUsername(username, request.getUsername());
|
||||
return ApiResult.success("用户名更新成功");
|
||||
}
|
||||
/**
|
||||
* 管理员修改用户昵称
|
||||
* URL: PUT /api/users/{userId}/nickname
|
||||
*/
|
||||
@PreAuthorize("hasAuthority('admin')")
|
||||
@PutMapping("/{userId}/nickname")
|
||||
public ApiResult<UserProfileResponse> setUserNickname(
|
||||
@PathVariable Long userId,
|
||||
@RequestBody @Validated SetUserNicknameRequest request) {
|
||||
return ApiResult.success(userProfileService.setUserNickname(userId, request.getNickname()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package cn.nopj.chaos_api.exception;
|
||||
|
||||
import cn.nopj.chaos_api.common.exceotion.BizException;
|
||||
import cn.nopj.chaos_api.model.ApiResult;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.security.authorization.AuthorizationDeniedException;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
@Slf4j
|
||||
@RestControllerAdvice
|
||||
@Order(Ordered.HIGHEST_PRECEDENCE)
|
||||
public class GlobalExceptionHandler {
|
||||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||
public ApiResult<String> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
|
||||
// 提取第一条错误提示
|
||||
String errorMsg = Objects.requireNonNull(e.getBindingResult().getFieldError()).getDefaultMessage();
|
||||
// 返回统一的错误格式
|
||||
return ApiResult.failed(errorMsg);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理自定义业务异常
|
||||
*/
|
||||
@ExceptionHandler(BizException.class)
|
||||
public ApiResult<?> handleBizException(BizException ex) {
|
||||
return ApiResult.failed(ex.getErrorCode().getHttpStatus(),ex.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 兜底处理:处理所有未知的 RuntimeException
|
||||
* 防止出现 500 错误页面,给前端返回友好的提示
|
||||
*/
|
||||
@ExceptionHandler(Exception.class)
|
||||
public ApiResult<?> handleAllExceptions(Exception ex) {
|
||||
log.error("服务器内部错误", ex);
|
||||
return ApiResult.failed("服务器内部错误,请联系管理员");
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理权限不足异常
|
||||
*/
|
||||
@ExceptionHandler(AuthorizationDeniedException.class)
|
||||
public ApiResult<?> handleAuthorizationDeniedException(AuthorizationDeniedException ex) {
|
||||
|
||||
return ApiResult.failed("权限不足,请求已登记");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package cn.nopj.chaos_api.security;
|
||||
|
||||
import cn.nopj.chaos_api.domain.entity.Role;
|
||||
import cn.nopj.chaos_api.domain.entity.User;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
public class CustomUserDetails implements UserDetails {
|
||||
private final User user;
|
||||
private final Collection<? extends GrantedAuthority> authorities;
|
||||
public CustomUserDetails(User user){
|
||||
this.user = user;
|
||||
List<Role> roles = user.getRoles();
|
||||
this.authorities = roles.stream().map(role -> new SimpleGrantedAuthority(role.getCode())).toList();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取该用户的权限集合
|
||||
* Spring Security 会根据这些权限进行鉴权(如 @PreAuthorize)
|
||||
*/
|
||||
@Override
|
||||
public Collection<? extends GrantedAuthority> getAuthorities() {
|
||||
return this.authorities;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPassword() {
|
||||
return user.getPassword();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsername() {
|
||||
return user.getUsername();
|
||||
}
|
||||
/**
|
||||
* 账户是否未过期
|
||||
* 返回 true 表示未过期,false 表示已过期(无法登录)
|
||||
*/
|
||||
@Override
|
||||
public boolean isAccountNonExpired() {
|
||||
return user.getAccountNonExpired(); // 这里可以根据业务逻辑修改,例如判断 expire_time
|
||||
}
|
||||
|
||||
/**
|
||||
* 账户是否未锁定
|
||||
* 返回 true 表示未锁定
|
||||
*/
|
||||
@Override
|
||||
public boolean isAccountNonLocked() {
|
||||
return user.getAccountNonLocked(); // 可根据由登录失败次数判断锁定逻辑
|
||||
}
|
||||
|
||||
/**
|
||||
* 凭证(密码)是否未过期
|
||||
* 返回 true 表示未过期
|
||||
*/
|
||||
@Override
|
||||
public boolean isCredentialsNonExpired() {
|
||||
return user.getCredentialsNonExpired(); // 可根据密码最后修改时间判断
|
||||
}
|
||||
|
||||
/**
|
||||
* 账户是否可用
|
||||
* 通常对应数据库中的 status 或 enabled 字段
|
||||
*/
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return user.getEnabled();
|
||||
}
|
||||
/**
|
||||
* 获取用户ID
|
||||
*/
|
||||
public Long getUserId() {
|
||||
return user.getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户昵称
|
||||
*/
|
||||
public String getNickname() {
|
||||
return user.getNickname();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户头像
|
||||
*/
|
||||
|
||||
public String getAvatar() {
|
||||
return user.getAvatar();
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,17 @@
|
||||
package cn.nopj.chaos_api.service;
|
||||
|
||||
import cn.nopj.chaos_api.domain.entity.User;
|
||||
import cn.nopj.chaos_api.dto.RegisterRequest;
|
||||
import cn.nopj.chaos_api.dto.response.AuthTokenResponse;
|
||||
|
||||
public interface AuthService {
|
||||
/**
|
||||
* 注册
|
||||
*/
|
||||
User register(RegisterRequest registerRequest);
|
||||
User register(User user);
|
||||
|
||||
/**
|
||||
* 登录
|
||||
* @return 生成的 JWT token
|
||||
*/
|
||||
String login(String username, String password);
|
||||
AuthTokenResponse login(String username, String password);
|
||||
}
|
||||
|
||||
@@ -1,23 +1,30 @@
|
||||
package cn.nopj.chaos_api.service.impl;
|
||||
|
||||
|
||||
|
||||
import cn.nopj.chaos_api.common.constants.ErrorCode;
|
||||
import cn.nopj.chaos_api.common.exceotion.BizException;
|
||||
import cn.nopj.chaos_api.domain.entity.Role;
|
||||
import cn.nopj.chaos_api.domain.entity.User;
|
||||
import cn.nopj.chaos_api.dto.RegisterRequest;
|
||||
import cn.nopj.chaos_api.dto.response.AuthTokenResponse;
|
||||
import cn.nopj.chaos_api.dto.response.UserProfileResponse;
|
||||
import cn.nopj.chaos_api.mapper.RoleMapper;
|
||||
import cn.nopj.chaos_api.mapper.UserMapper;
|
||||
import cn.nopj.chaos_api.security.CustomUserDetails;
|
||||
import cn.nopj.chaos_api.service.AuthService;
|
||||
import cn.nopj.chaos_api.util.JwtTokenUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.security.authentication.*;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
public class AuthServiceImpl implements AuthService {
|
||||
|
||||
|
||||
@@ -29,37 +36,70 @@ public class AuthServiceImpl implements AuthService {
|
||||
private JwtTokenUtil jwtTokenUtil;
|
||||
@Autowired
|
||||
private AuthenticationManager authenticationManager;
|
||||
@Value("${jwt.tokenHead}")
|
||||
private String tokenHead;
|
||||
@Value("${auth.def-role-code}")
|
||||
private String defRoleCode;
|
||||
@Autowired
|
||||
private RoleMapper roleMapper;
|
||||
|
||||
@Override
|
||||
public User register(RegisterRequest registerRequest) {
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public User register(User user) {
|
||||
|
||||
// 检查用户名是否已存在
|
||||
if (userMapper.selectOne(new QueryWrapper<User>().eq("username", registerRequest.getUsername())) != null) {
|
||||
if (userMapper.selectByUsername(user.getUsername()) != null) {
|
||||
// 在实际项目中,这里应该抛出自定义异常
|
||||
return null;
|
||||
throw new BizException(ErrorCode.USERNAME_EXISTS);
|
||||
}
|
||||
User user = new User();
|
||||
user.setUsername(registerRequest.getUsername());
|
||||
// 使用 BCrypt 加密密码
|
||||
user.setPassword(passwordEncoder.encode(registerRequest.getPassword()));
|
||||
user.setPassword(passwordEncoder.encode(user.getPassword()));
|
||||
userMapper.insert(user);
|
||||
// 你可以在这里为新用户分配默认角色
|
||||
userMapper.insertUserRole(user.getId(), 2L);
|
||||
|
||||
Role role = roleMapper.selectByCode(defRoleCode);
|
||||
userMapper.insertUserRole(user.getId(), role.getId());
|
||||
|
||||
log.info("用户注册成功: {}", user.getUsername());
|
||||
return user;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String login(String username, String password) {
|
||||
public AuthTokenResponse login(String username, String password) {
|
||||
log.info("用户登录: {}", username);
|
||||
// 尝试进行身份验证
|
||||
try{
|
||||
Authentication authentication = authenticationManager.authenticate(
|
||||
new UsernamePasswordAuthenticationToken(username, password)
|
||||
);
|
||||
log.info("用户登录成功: {}", username);
|
||||
// 将认证结果保存在 SecurityContextHolder 中
|
||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||
|
||||
CustomUserDetails userDetails = (CustomUserDetails)authentication.getPrincipal();
|
||||
// 获取用户详情
|
||||
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
|
||||
if (!userDetails.isEnabled()) {
|
||||
return null;
|
||||
throw new BizException(ErrorCode.USER_NOT_ENABLED);
|
||||
}
|
||||
|
||||
|
||||
AuthTokenResponse res = new AuthTokenResponse();
|
||||
res.setToken(jwtTokenUtil.generateToken(userDetails));
|
||||
res.setTokenHead(tokenHead);
|
||||
return res;
|
||||
|
||||
}catch (BadCredentialsException | InternalAuthenticationServiceException e) {
|
||||
log.error("用户登录失败",e);
|
||||
throw new BizException(ErrorCode.USER_NOT_EXISTS_OR_PASSWORD_WRONG);
|
||||
} catch (DisabledException e) {
|
||||
throw new BizException(ErrorCode.USER_NOT_ENABLED);
|
||||
} catch (BizException e){
|
||||
throw e;
|
||||
}
|
||||
catch (Exception e) {
|
||||
|
||||
log.error("用户登录异常",e);
|
||||
throw new BizException(ErrorCode.SYSTEM_ERROR);
|
||||
}
|
||||
// 生成 JWT
|
||||
return jwtTokenUtil.generateToken(userDetails);
|
||||
}
|
||||
}
|
||||
@@ -1,49 +1,36 @@
|
||||
package cn.nopj.chaos_api.service.impl;
|
||||
|
||||
import cn.nopj.chaos_api.common.constants.ErrorCode;
|
||||
import cn.nopj.chaos_api.common.exceotion.BizException;
|
||||
import cn.nopj.chaos_api.domain.entity.User;
|
||||
import cn.nopj.chaos_api.mapper.UserMapper;
|
||||
import cn.nopj.chaos_api.security.CustomUserDetails;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
public class UserDetailsServiceImpl implements UserDetailsService {
|
||||
|
||||
@Autowired
|
||||
UserMapper userMapper;
|
||||
@Override
|
||||
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
|
||||
public CustomUserDetails loadUserByUsername(String username) {
|
||||
// 1. 从数据库查询用户
|
||||
User user = userMapper.selectOne(new QueryWrapper<User>().eq("username", username));
|
||||
User user = userMapper.findUserWithRolesByUsername(username);
|
||||
if (user == null) {
|
||||
throw new UsernameNotFoundException("用户不存在");
|
||||
throw new BizException(ErrorCode.USER_NOT_EXISTS);
|
||||
}
|
||||
|
||||
// 2. 查询该用户的权限信息 (角色 + 权限)
|
||||
List<String> authorities = userMapper.findAuthoritiesByUsername(username);
|
||||
|
||||
// 3. 将权限字符串列表转换为 GrantedAuthority 集合
|
||||
List<GrantedAuthority> grantedAuthorities = authorities.stream()
|
||||
.map(SimpleGrantedAuthority::new)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 4. 构建并返回 Spring Security 的 User 对象
|
||||
return new org.springframework.security.core.userdetails.User(
|
||||
user.getUsername(),
|
||||
user.getPassword(),
|
||||
user.getEnabled(),
|
||||
user.getAccountNonExpired(),
|
||||
user.getCredentialsNonExpired(),
|
||||
user.getAccountNonLocked(),
|
||||
grantedAuthorities
|
||||
);
|
||||
return new CustomUserDetails(user);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,21 @@
|
||||
package cn.nopj.chaos_api.util;
|
||||
|
||||
import cn.nopj.chaos_api.security.CustomUserDetails;
|
||||
import com.auth0.jwt.JWT;
|
||||
import com.auth0.jwt.JWTVerifier;
|
||||
import com.auth0.jwt.algorithms.Algorithm;
|
||||
import com.auth0.jwt.interfaces.DecodedJWT;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
||||
@Slf4j
|
||||
@@ -53,10 +57,9 @@ public class JwtTokenUtil {
|
||||
/**
|
||||
* 验证 Token 是否有效
|
||||
*/
|
||||
public boolean validateToken(String token, UserDetails userDetails) {
|
||||
public boolean validateToken(String token) {
|
||||
try {
|
||||
String username = getUsernameFromToken(token);
|
||||
return username != null && username.equals(userDetails.getUsername()) && !isTokenExpired(token);
|
||||
return decodeToken(token) != null;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
@@ -65,14 +68,24 @@ public class JwtTokenUtil {
|
||||
/**
|
||||
* 生成 Token
|
||||
*/
|
||||
public String generateToken(UserDetails userDetails) {
|
||||
public String generateToken(CustomUserDetails userDetails) {
|
||||
Date now = new Date();
|
||||
Date expiryDate = new Date(now.getTime() + expiration * 1000);
|
||||
|
||||
List<String> authorities = userDetails.getAuthorities()
|
||||
.stream()
|
||||
.map(GrantedAuthority::getAuthority)
|
||||
.toList();
|
||||
|
||||
|
||||
return JWT.create()
|
||||
.withSubject(userDetails.getUsername())
|
||||
.withIssuedAt(now)
|
||||
.withExpiresAt(expiryDate)
|
||||
.withClaim("authorities", authorities)
|
||||
.withClaim("userId", userDetails.getUserId())
|
||||
.withClaim("nickname", userDetails.getNickname())
|
||||
.withClaim("avatar", userDetails.getAvatar())
|
||||
.sign(algorithm);
|
||||
}
|
||||
|
||||
@@ -86,4 +99,34 @@ public class JwtTokenUtil {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 解析 Token
|
||||
* @param token Token
|
||||
* @return 解析后的 Token
|
||||
*/
|
||||
private DecodedJWT decodeToken(String token) {
|
||||
try {
|
||||
return JWT.decode(token);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 从 Token 中获取权限列表
|
||||
* @param token Token
|
||||
* @return 权限列表
|
||||
*/
|
||||
public List<String> getAuthoritiesFromToken(String token){
|
||||
DecodedJWT decodedJWT = decodeToken(token);
|
||||
if (decodedJWT == null){
|
||||
return null;
|
||||
}
|
||||
return decodedJWT.getClaim("authorities")
|
||||
.asList(String.class);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -22,6 +22,8 @@ spring:
|
||||
output:
|
||||
ansi:
|
||||
enabled: always
|
||||
profiles:
|
||||
active: dev
|
||||
|
||||
mybatis-plus:
|
||||
mapper-locations: classpath*:/mapper/**/*.xml
|
||||
@@ -29,14 +31,19 @@ mybatis-plus:
|
||||
global-config:
|
||||
db-config:
|
||||
id-type: auto
|
||||
logic-not-delete-value: "null"
|
||||
logic-delete-value: "NOW()"
|
||||
|
||||
configuration:
|
||||
map-underscore-to-camel-case: true
|
||||
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
|
||||
|
||||
|
||||
jwt:
|
||||
tokenHeader: Authorization
|
||||
tokenHead: Chaos
|
||||
secret: zHANgcHao@1995!20250506
|
||||
expiration: 604800
|
||||
|
||||
|
||||
auth:
|
||||
def-role-code: user
|
||||
|
||||
257
chaos_api_web/src/main/resources/data.sql
Normal file
257
chaos_api_web/src/main/resources/data.sql
Normal file
@@ -0,0 +1,257 @@
|
||||
/*
|
||||
Navicat Premium Data Transfer
|
||||
Source Schema : chaos
|
||||
Target Server Type : MySQL
|
||||
Target Server Version : 8.0+
|
||||
Date: 2024-06-12
|
||||
*/
|
||||
|
||||
SET NAMES utf8mb4;
|
||||
SET FOREIGN_KEY_CHECKS = 0;
|
||||
|
||||
-- ----------------------------
|
||||
-- 1. 设备类型表 (保持原有结构)
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `t_device_type`;
|
||||
CREATE TABLE `t_device_type` (
|
||||
`id` bigint unsigned auto_increment comment '主键ID'
|
||||
primary key,
|
||||
`name` varchar(128) not null comment '设备类型名称',
|
||||
`code` varchar(64) null comment '设备类型唯一编码',
|
||||
`parent_id` bigint unsigned default 0 null comment '父级类型ID (0表示顶级)',
|
||||
`remark` varchar(500) default '' null comment '备注',
|
||||
`create_time` datetime default current_timestamp() not null comment '创建时间',
|
||||
`update_time` datetime default current_timestamp() not null on update current_timestamp() comment '更新时间',
|
||||
`delete_time` datetime null comment '逻辑删除时间',
|
||||
constraint uk_code
|
||||
unique (`code`),
|
||||
constraint uk_name
|
||||
unique (`name`)
|
||||
) comment '资产管理-设备类型表';
|
||||
|
||||
create index idx_parent_id on `t_device_type` (`parent_id`);
|
||||
|
||||
|
||||
-- ----------------------------
|
||||
-- 2. 设备信息表 (保持原有结构)
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `t_device`;
|
||||
CREATE TABLE `t_device` (
|
||||
`id` bigint unsigned auto_increment comment '主键ID'
|
||||
primary key,
|
||||
`name` varchar(128) not null comment '设备名称',
|
||||
`model` varchar(128) null comment '设备型号',
|
||||
`type_id` bigint unsigned null comment '设备类型ID (外键关联设备类型表)',
|
||||
`location_id` bigint unsigned null comment '位置ID (外键关联位置表)',
|
||||
`snmp_community` varchar(128) null comment 'SNMP团体名 (建议加密或隐藏)',
|
||||
`manufacturer` varchar(128) null comment '设备制造商',
|
||||
`purchase_date` date null comment '采购日期',
|
||||
`status` tinyint unsigned default 1 null comment '设备状态 (1:在线, 0:离线, 2:维护中)',
|
||||
`remark` varchar(500) default '' null comment '备注',
|
||||
`create_time` datetime default current_timestamp() not null comment '创建时间',
|
||||
`update_time` datetime default current_timestamp() not null on update current_timestamp() comment '更新时间',
|
||||
`delete_time` datetime null comment '逻辑删除时间',
|
||||
constraint fk_dev_type_id
|
||||
foreign key (`type_id`) references `t_device_type` (`id`)
|
||||
on update cascade on delete set null
|
||||
) comment '资产管理-设备信息表';
|
||||
|
||||
create index idx_location_id on `t_device` (`location_id`);
|
||||
create index idx_type_id on `t_device` (`type_id`);
|
||||
|
||||
|
||||
-- ----------------------------
|
||||
-- 3. 网络接口表 (结构调整:移除IP/VLAN,仅保留物理/链路属性)
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `t_network_interface`;
|
||||
CREATE TABLE `t_network_interface` (
|
||||
`id` bigint unsigned auto_increment comment '主键ID'
|
||||
primary key,
|
||||
`device_id` bigint unsigned not null comment '设备ID',
|
||||
`parent_id` bigint unsigned null comment '父接口ID (用于子接口/聚合口成员)',
|
||||
`name` varchar(64) not null comment '接口名称 (如: eth0, GE0/0/1, Port-Channel1)',
|
||||
`type` tinyint unsigned default 1 not null comment '接口类型 (1:物理口, 2:聚合口, 3:虚拟口)',
|
||||
`mac_address` varchar(17) null comment 'MAC地址 (格式: AA:BB:CC:DD:EE:FF)',
|
||||
`port_speed` int unsigned default 0 null comment '物理端口速率 (Mbps)',
|
||||
`duplex` tinyint unsigned default 1 null comment '双工模式 (1:全双工, 2:半双工, 3:自适应)',
|
||||
`status` tinyint unsigned default 1 null comment '接口运行状态 (1:UP, 0:DOWN)',
|
||||
`remark` varchar(500) default '' null comment '备注',
|
||||
`create_time` datetime default current_timestamp() not null comment '创建时间',
|
||||
`update_time` datetime default current_timestamp() not null on update current_timestamp() comment '更新时间',
|
||||
`delete_time` datetime null comment '逻辑删除时间',
|
||||
constraint fk_ni_device_id
|
||||
foreign key (`device_id`) references `t_device` (`id`)
|
||||
on update cascade on delete cascade,
|
||||
constraint fk_ni_parent_id
|
||||
foreign key (`parent_id`) references `t_network_interface` (`id`)
|
||||
on update cascade on delete set null
|
||||
) comment '资产管理-网络接口基础表';
|
||||
|
||||
create index idx_ni_device_id on `t_network_interface` (`device_id`);
|
||||
create index idx_ni_mac_address on `t_network_interface` (`mac_address`);
|
||||
|
||||
|
||||
-- ----------------------------
|
||||
-- 4. 接口地址配置表 (新增:核心表,支持多IP/多VLAN/广播地址)
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `t_interface_address_config`;
|
||||
CREATE TABLE `t_interface_address_config` (
|
||||
`id` bigint unsigned auto_increment comment '主键ID'
|
||||
primary key,
|
||||
`interface_id` bigint unsigned not null comment '关联物理接口ID',
|
||||
`vlan_id` smallint unsigned default 0 null comment 'VLAN ID (0或NULL表示Native/Untagged)',
|
||||
`ip_address` varchar(45) null comment 'IP地址 (支持IPv4/IPv6)',
|
||||
`subnet_mask` varchar(45) null comment '子网掩码/CIDR前缀',
|
||||
`gateway_ip` varchar(45) null comment '网关IP地址',
|
||||
`broadcast_address` varchar(45) null comment '广播地址 (新增)',
|
||||
`is_primary` tinyint unsigned default 1 not null comment '是否为主IP (1:主IP, 0:从IP/Alias)',
|
||||
`is_dhcp` tinyint unsigned default 0 not null comment '是否启用DHCP (1:启用, 0:静态)',
|
||||
`mtu` int unsigned default 1500 null comment 'MTU值',
|
||||
`status` tinyint unsigned default 1 null comment '配置状态 (1:启用, 0:禁用)',
|
||||
`remark` varchar(500) default '' null comment '备注',
|
||||
`create_time` datetime default current_timestamp() not null comment '创建时间',
|
||||
`update_time` datetime default current_timestamp() not null on update current_timestamp() comment '更新时间',
|
||||
`delete_time` datetime null comment '逻辑删除时间',
|
||||
constraint fk_iac_interface_id
|
||||
foreign key (`interface_id`) references `t_network_interface` (`id`)
|
||||
on update cascade on delete cascade
|
||||
) comment '资产管理-接口地址与VLAN配置表';
|
||||
|
||||
create index idx_iac_interface_id on `t_interface_address_config` (`interface_id`);
|
||||
create index idx_iac_ip_address on `t_interface_address_config` (`ip_address`);
|
||||
|
||||
|
||||
-- ----------------------------
|
||||
-- 5. DNS 服务器字典表 (新增:复用DNS)
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `t_dns_server`;
|
||||
CREATE TABLE `t_dns_server` (
|
||||
`id` bigint unsigned auto_increment comment '主键ID'
|
||||
primary key,
|
||||
`dns_address` varchar(45) not null comment 'DNS服务器IP地址',
|
||||
`name` varchar(64) null comment 'DNS名称 (如: Google DNS)',
|
||||
`description` varchar(255) default '' null comment '描述',
|
||||
`create_time` datetime default current_timestamp() not null comment '创建时间',
|
||||
`update_time` datetime default current_timestamp() not null on update current_timestamp() comment '更新时间',
|
||||
`delete_time` datetime null comment '逻辑删除时间',
|
||||
constraint uk_dns_address
|
||||
unique (`dns_address`)
|
||||
) comment '资产管理-DNS服务器字典表';
|
||||
|
||||
|
||||
-- ----------------------------
|
||||
-- 6. 接口配置与DNS关联表 (新增:多对多关系)
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `t_interface_dns_mapping`;
|
||||
CREATE TABLE `t_interface_dns_mapping` (
|
||||
`id` bigint unsigned auto_increment comment '主键ID'
|
||||
primary key,
|
||||
`config_id` bigint unsigned not null comment '接口配置ID (关联 t_interface_address_config)',
|
||||
`dns_server_id` bigint unsigned not null comment 'DNS服务器ID (关联 t_dns_server)',
|
||||
`priority` int unsigned default 1 not null comment '优先级 (数值越小优先级越高)',
|
||||
`create_time` datetime default current_timestamp() not null comment '创建时间',
|
||||
constraint uk_config_dns
|
||||
unique (`config_id`, `dns_server_id`),
|
||||
constraint fk_idm_config_id
|
||||
foreign key (`config_id`) references `t_interface_address_config` (`id`)
|
||||
on update cascade on delete cascade,
|
||||
constraint fk_idm_dns_id
|
||||
foreign key (`dns_server_id`) references `t_dns_server` (`id`)
|
||||
on update cascade on delete cascade
|
||||
) comment '资产管理-接口配置DNS关联表';
|
||||
|
||||
create index idx_idm_config_id on `t_interface_dns_mapping` (`config_id`);
|
||||
|
||||
SET FOREIGN_KEY_CHECKS = 1;
|
||||
|
||||
create table chaos.t_permission
|
||||
(
|
||||
id bigint unsigned auto_increment comment '主键ID'
|
||||
primary key,
|
||||
parent_id bigint unsigned default 0 not null comment '父权限ID (0为顶级)',
|
||||
name varchar(64) not null comment '权限名称',
|
||||
code varchar(128) not null comment '权限标识/资源路径 (RESTful 风格或权限点)',
|
||||
type tinyint unsigned default 1 not null comment '类型 (1:目录, 2:菜单, 3:按钮)',
|
||||
sort_order int default 0 not null comment '排序 (数值越小越靠前)',
|
||||
create_time datetime default current_timestamp() not null comment '创建时间',
|
||||
update_time datetime default current_timestamp() not null on update current_timestamp() comment '更新时间',
|
||||
delete_time datetime null,
|
||||
constraint uk_code
|
||||
unique (code)
|
||||
)
|
||||
comment '系统管理-权限表';
|
||||
|
||||
create index idx_parent_id
|
||||
on chaos.t_permission (parent_id);
|
||||
|
||||
create table chaos.t_role
|
||||
(
|
||||
id bigint unsigned auto_increment comment '主键ID'
|
||||
primary key,
|
||||
name varchar(64) not null comment '角色名称 (如: 管理员)',
|
||||
code varchar(64) not null comment '角色标识 (如: admin)',
|
||||
status tinyint unsigned default 1 not null comment '状态 (1:正常, 0:停用)',
|
||||
remark varchar(500) default '' null comment '备注',
|
||||
create_time datetime default current_timestamp() not null comment '创建时间',
|
||||
update_time datetime default current_timestamp() not null on update current_timestamp() comment '更新时间',
|
||||
delete_time datetime null,
|
||||
constraint uk_code
|
||||
unique (code)
|
||||
)
|
||||
comment '系统管理-角色表';
|
||||
|
||||
create table chaos.t_role_permission
|
||||
(
|
||||
id bigint unsigned auto_increment comment '主键ID (代理键)'
|
||||
primary key,
|
||||
role_id bigint unsigned not null comment '角色ID',
|
||||
permission_id bigint unsigned not null comment '权限ID',
|
||||
constraint uk_role_permission
|
||||
unique (role_id, permission_id),
|
||||
constraint fk_rp_permission_id
|
||||
foreign key (permission_id) references chaos.t_permission (id)
|
||||
on update cascade on delete cascade,
|
||||
constraint fk_rp_role_id
|
||||
foreign key (role_id) references chaos.t_role (id)
|
||||
on update cascade on delete cascade
|
||||
)
|
||||
comment '系统管理-角色权限关联表';
|
||||
|
||||
create table chaos.t_user
|
||||
(
|
||||
id bigint unsigned auto_increment comment '主键ID'
|
||||
primary key,
|
||||
username varchar(64) not null comment '用户名/登录名',
|
||||
password varchar(255) not null comment '密码(加密存储,建议BCrypt或Argon2)',
|
||||
nickname varchar(64) default '' null comment '用户昵称',
|
||||
avatar varchar(255) null comment '头像',
|
||||
enabled tinyint unsigned default 1 not null comment '状态 (1:启用, 0:禁用)',
|
||||
account_non_expired tinyint unsigned default 1 not null comment '账号未过期 (1:是, 0:否)',
|
||||
credentials_non_expired tinyint unsigned default 1 not null comment '凭证未过期 (1:是, 0:否)',
|
||||
account_non_locked tinyint unsigned default 1 not null comment '账号未锁定 (1:是, 0:否)',
|
||||
remark varchar(500) default '' null comment '备注信息',
|
||||
create_time datetime default current_timestamp() not null comment '创建时间',
|
||||
update_time datetime default current_timestamp() not null on update current_timestamp() comment '更新时间',
|
||||
delete_time datetime null,
|
||||
constraint uk_username
|
||||
unique (username)
|
||||
)
|
||||
comment '系统管理-用户表';
|
||||
|
||||
create table chaos.t_user_role
|
||||
(
|
||||
id bigint unsigned auto_increment comment '主键ID (代理键)'
|
||||
primary key,
|
||||
user_id bigint unsigned not null comment '用户ID',
|
||||
role_id bigint unsigned not null comment '角色ID',
|
||||
constraint uk_user_role
|
||||
unique (user_id, role_id),
|
||||
constraint fk_ur_role_id
|
||||
foreign key (role_id) references chaos.t_role (id)
|
||||
on update cascade on delete cascade,
|
||||
constraint fk_ur_user_id
|
||||
foreign key (user_id) references chaos.t_user (id)
|
||||
on update cascade on delete cascade
|
||||
)
|
||||
comment '系统管理-用户角色关联表';
|
||||
|
||||
40
pom.xml
40
pom.xml
@@ -31,38 +31,38 @@
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
<version>3.5.3</version>
|
||||
<version>3.5.7</version>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-security -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
<version>3.5.3</version>
|
||||
<version>3.5.7</version>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-autoconfigure -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-autoconfigure</artifactId>
|
||||
<version>3.5.3</version>
|
||||
<version>3.5.7</version>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-redis -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-redis</artifactId>
|
||||
<version>3.5.3</version>
|
||||
<version>3.5.7</version>
|
||||
</dependency>
|
||||
|
||||
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-websocket -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-websocket</artifactId>
|
||||
<version>3.5.3</version>
|
||||
<version>3.5.7</version>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-spring-boot3-starter -->
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
|
||||
<version>3.5.12</version>
|
||||
<version>3.5.14</version>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/com.mysql/mysql-connector-j -->
|
||||
<dependency>
|
||||
@@ -74,7 +74,7 @@
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus</artifactId>
|
||||
<version>3.5.12</version>
|
||||
<version>3.5.14</version>
|
||||
</dependency>
|
||||
|
||||
<!-- https://mvnrepository.com/artifact/com.alibaba/druid-spring-boot-starter -->
|
||||
@@ -111,7 +111,7 @@
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<version>3.5.3</version>
|
||||
<version>3.5.7</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -119,16 +119,36 @@
|
||||
<artifactId>logback-classic</artifactId>
|
||||
<version>1.5.18</version>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-amqp -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-amqp</artifactId>
|
||||
<version>3.5.3</version>
|
||||
<version>3.5.7</version>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/org.springframework/spring-web -->
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-web</artifactId>
|
||||
<version>6.2.9</version>
|
||||
<version>6.2.13</version>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-validation -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
<version>3.5.7</version>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-context</artifactId>
|
||||
<version>6.2.13</version>
|
||||
</dependency>
|
||||
|
||||
<!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-jsqlparser -->
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-jsqlparser</artifactId>
|
||||
<version>3.5.14</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
218
upload.html
218
upload.html
@@ -1,218 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>分片上传示例</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
background-color: #f0f2f5;
|
||||
}
|
||||
.container {
|
||||
background: white;
|
||||
padding: 2rem 3rem;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 8px 24px rgba(0,0,0,0.1);
|
||||
text-align: center;
|
||||
width: 90%;
|
||||
max-width: 500px;
|
||||
}
|
||||
h2 {
|
||||
color: #333;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
#fileInput {
|
||||
display: none;
|
||||
}
|
||||
.file-label {
|
||||
display: inline-block;
|
||||
padding: 10px 20px;
|
||||
background-color: #e9ecef;
|
||||
border: 2px dashed #ced4da;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s, border-color 0.3s;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.file-label:hover {
|
||||
background-color: #dee2e6;
|
||||
border-color: #adb5bd;
|
||||
}
|
||||
#fileName {
|
||||
font-style: italic;
|
||||
color: #6c757d;
|
||||
margin-bottom: 1.5rem;
|
||||
min-height: 20px;
|
||||
}
|
||||
#uploadButton {
|
||||
width: 100%;
|
||||
padding: 12px 20px;
|
||||
border: none;
|
||||
background: linear-gradient(45deg, #007bff, #0056b3);
|
||||
color: white;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
#uploadButton:disabled {
|
||||
background: #adb5bd;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
#uploadButton:not(:disabled):hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0, 123, 255, 0.3);
|
||||
}
|
||||
.progress-bar {
|
||||
width: 100%;
|
||||
background-color: #e9ecef;
|
||||
border-radius: 8px;
|
||||
margin-top: 1.5rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
.progress {
|
||||
width: 0%;
|
||||
height: 24px;
|
||||
background: linear-gradient(45deg, #28a745, #218838);
|
||||
border-radius: 8px;
|
||||
text-align: center;
|
||||
color: white;
|
||||
line-height: 24px;
|
||||
font-weight: bold;
|
||||
transition: width 0.4s ease-in-out;
|
||||
}
|
||||
#status {
|
||||
margin-top: 1rem;
|
||||
color: #212529;
|
||||
font-weight: 500;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="container">
|
||||
<h2>视频分片上传</h2>
|
||||
<label for="fileInput" class="file-label">选择文件</label>
|
||||
<input type="file" id="fileInput" />
|
||||
<div id="fileName">未选择文件</div>
|
||||
<button id="uploadButton">上传</button>
|
||||
<div class="progress-bar">
|
||||
<div class="progress" id="progressBar">0%</div>
|
||||
</div>
|
||||
<p id="status"></p>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
|
||||
<script>
|
||||
const fileInput = document.getElementById('fileInput');
|
||||
const uploadButton = document.getElementById('uploadButton');
|
||||
const progressBar = document.getElementById('progressBar');
|
||||
const statusEl = document.getElementById('status');
|
||||
const fileNameEl = document.getElementById('fileName');
|
||||
|
||||
// =================================================================
|
||||
// 【重要】请将这里替换为您通过登录接口获取到的真实JWT Token
|
||||
const JWT_TOKEN = "Chaos "
|
||||
+ "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJjaGFvcyIsImlhdCI6MTc1MjkwNjk4NCwiZXhwIjoxNzUzNTExNzg0fQ.fG8bSGqji9BfKVoSpMKy5GvSQuzXNdlc7Km94nkuUyPOPVBcGLSBafzSBONxn7ECYcGhS0jmmNK-_z207zy-UA"
|
||||
// =================================================================
|
||||
|
||||
const api = axios.create({
|
||||
baseURL: 'http://localhost:18888/api/video', // 确保这个基础URL与您的后端服务匹配
|
||||
headers: { 'Authorization': JWT_TOKEN }
|
||||
});
|
||||
|
||||
fileInput.addEventListener('change', () => {
|
||||
if (fileInput.files.length > 0) {
|
||||
fileNameEl.textContent = fileInput.files[0].name;
|
||||
} else {
|
||||
fileNameEl.textContent = '未选择文件';
|
||||
}
|
||||
});
|
||||
|
||||
uploadButton.addEventListener('click', async () => {
|
||||
const file = fileInput.files[0];
|
||||
if (!file) {
|
||||
alert('请先选择文件!');
|
||||
return;
|
||||
}
|
||||
|
||||
uploadButton.disabled = true;
|
||||
statusEl.textContent = '正在初始化上传...';
|
||||
updateProgress(0);
|
||||
|
||||
try {
|
||||
// 步骤 1: 初始化上传,获取 uploadId 和 chunkSize
|
||||
const initResponse = await api.post('/init', {
|
||||
fileName: file.name,
|
||||
totalSize: file.size
|
||||
});
|
||||
|
||||
const { uploadId, chunkSize } = initResponse.data.data;
|
||||
statusEl.textContent = '初始化成功,开始上传分片...';
|
||||
|
||||
// 步骤 2: 分片并并发上传
|
||||
const totalChunks = Math.ceil(file.size / chunkSize);
|
||||
const uploadPromises = [];
|
||||
|
||||
for (let i = 0; i < totalChunks; i++) {
|
||||
const start = i * chunkSize;
|
||||
const end = Math.min(start + chunkSize, file.size);
|
||||
const chunk = file.slice(start, end);
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('chunk', chunk);
|
||||
formData.append('uploadId', uploadId);
|
||||
formData.append('chunkIndex', i);
|
||||
|
||||
const promise = api.post('/chunk', formData, {
|
||||
onUploadProgress: (progressEvent) => {
|
||||
// 这个回调是单个分片的进度,我们在这里简单更新总进度
|
||||
const percentCompleted = Math.round(((i * chunkSize) + progressEvent.loaded) * 100 / file.size);
|
||||
updateProgress(percentCompleted);
|
||||
}
|
||||
});
|
||||
uploadPromises.push(promise);
|
||||
}
|
||||
|
||||
// 等待所有分片上传完成
|
||||
await Promise.all(uploadPromises);
|
||||
statusEl.textContent = '所有分片上传完毕,正在合并文件...';
|
||||
|
||||
// 步骤 3: 通知后端合并文件
|
||||
const mergeResponse = await api.post('/merge', {
|
||||
uploadId: uploadId,
|
||||
fileName: file.name
|
||||
});
|
||||
|
||||
statusEl.textContent = mergeResponse.data.message || '文件上传成功,后台处理中!';
|
||||
updateProgress(100);
|
||||
|
||||
} catch (error) {
|
||||
console.error('上传失败:', error);
|
||||
statusEl.textContent = '上传失败: ' + (error.response?.data?.message || error.message);
|
||||
updateProgress(0, true); // 出错时重置进度条为红色
|
||||
} finally {
|
||||
uploadButton.disabled = false;
|
||||
}
|
||||
});
|
||||
|
||||
function updateProgress(percentage, isError = false) {
|
||||
progressBar.style.width = percentage + '%';
|
||||
progressBar.textContent = percentage + '%';
|
||||
if (isError) {
|
||||
progressBar.style.background = 'linear-gradient(45deg, #dc3545, #c82333)';
|
||||
} else {
|
||||
progressBar.style.background = 'linear-gradient(45deg, #28a745, #218838)';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user