Compare commits
31 Commits
2f946ec596
...
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 |
@@ -29,5 +29,21 @@
|
|||||||
<groupId>com.alibaba.fastjson2</groupId>
|
<groupId>com.alibaba.fastjson2</groupId>
|
||||||
<artifactId>fastjson2</artifactId>
|
<artifactId>fastjson2</artifactId>
|
||||||
</dependency>
|
</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>
|
</dependencies>
|
||||||
</project>
|
</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>
|
<dependency>
|
||||||
<groupId>org.mariadb.jdbc</groupId>
|
<groupId>org.mariadb.jdbc</groupId>
|
||||||
<artifactId>mariadb-java-client</artifactId>
|
<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>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</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 cn.nopj.chaos_api.domain.entity.Role;
|
||||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
import org.apache.ibatis.annotations.Mapper;
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.apache.ibatis.annotations.Select;
|
||||||
|
|
||||||
@Mapper
|
@Mapper
|
||||||
public interface RoleMapper extends BaseMapper<Role> {
|
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;
|
package cn.nopj.chaos_api.mapper;
|
||||||
|
|
||||||
|
import cn.nopj.chaos_api.domain.entity.Role;
|
||||||
import cn.nopj.chaos_api.domain.entity.User;
|
import cn.nopj.chaos_api.domain.entity.User;
|
||||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
import org.apache.ibatis.annotations.Mapper;
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
import org.apache.ibatis.annotations.Param;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
import org.apache.ibatis.annotations.Select;
|
import org.apache.ibatis.annotations.*;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -32,9 +34,108 @@ public interface UserMapper extends BaseMapper<User> {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 插入用户角色关系
|
* 插入用户角色关系
|
||||||
* @param id 用户id
|
* 注意:已将注解由 @Select 修正为 @Insert,并优化了参数命名。
|
||||||
* @param i 角色id
|
* 由于 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})")
|
@Insert("INSERT INTO t_user_role (user_id, role_id) VALUES (#{userId}, #{roleId})")
|
||||||
void insertUserRole(@Param("userId") Long id, @Param("roleId") Long i);
|
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>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
</properties>
|
</properties>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-validation</artifactId>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.baomidou</groupId>
|
<groupId>com.baomidou</groupId>
|
||||||
<artifactId>mybatis-plus</artifactId>
|
<artifactId>mybatis-plus</artifactId>
|
||||||
@@ -29,5 +33,11 @@
|
|||||||
<artifactId>lombok</artifactId>
|
<artifactId>lombok</artifactId>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>jakarta.validation</groupId>
|
||||||
|
<artifactId>jakarta.validation-api</artifactId>
|
||||||
|
<version>3.0.2</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</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;
|
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 com.baomidou.mybatisplus.annotation.TableName;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统管理-用户表
|
||||||
|
* @TableName t_user
|
||||||
|
*/
|
||||||
@Data
|
@Data
|
||||||
@TableName("t_user")
|
@TableName(value ="t_user")
|
||||||
public class User implements Serializable {
|
|
||||||
|
public class User {
|
||||||
|
/**
|
||||||
|
* 主键ID
|
||||||
|
*/
|
||||||
|
@TableId
|
||||||
private Long id;
|
private Long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户名/登录名
|
||||||
|
*/
|
||||||
private String username;
|
private String username;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 密码(加密存储,建议BCrypt)
|
||||||
|
*/
|
||||||
private String password;
|
private String password;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户昵称
|
||||||
|
*/
|
||||||
|
private String nickname;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 头像
|
||||||
|
*/
|
||||||
|
private String avatar;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 状态 (1:启用, 0:禁用)
|
||||||
|
*/
|
||||||
private Boolean enabled;
|
private Boolean enabled;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 账号未过期 (1:是, 0:否)
|
||||||
|
*/
|
||||||
private Boolean accountNonExpired;
|
private Boolean accountNonExpired;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 凭证未过期 (1:是, 0:否)
|
||||||
|
*/
|
||||||
private Boolean credentialsNonExpired;
|
private Boolean credentialsNonExpired;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 账号未锁定 (1:是, 0:否)
|
||||||
|
*/
|
||||||
private Boolean accountNonLocked;
|
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>
|
<artifactId>chaos_api_domain</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<properties>
|
<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>
|
<artifactId>spring-web</artifactId>
|
||||||
<scope>compile</scope>
|
<scope>compile</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.security</groupId>
|
||||||
|
<artifactId>spring-security-crypto</artifactId>
|
||||||
|
<version>6.5.6</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</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>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-web</artifactId>
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
@@ -69,7 +68,6 @@
|
|||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
package cn.nopj.chaos_api;
|
package cn.nopj.chaos_api;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.boot.SpringApplication;
|
import org.springframework.boot.SpringApplication;
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
|
||||||
@SpringBootApplication(scanBasePackages = "cn.nopj.chaos_api")
|
@SpringBootApplication(scanBasePackages = "cn.nopj.chaos_api")
|
||||||
|
@Slf4j
|
||||||
public class ChaosApiWebApplication {
|
public class ChaosApiWebApplication {
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
SpringApplication.run(ChaosApiWebApplication.class, 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();
|
return authenticationConfiguration.getAuthenticationManager();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||||
http
|
http
|
||||||
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
||||||
.authorizeHttpRequests(auth -> auth
|
.authorizeHttpRequests(auth -> auth
|
||||||
// 允许所有对 /api/public/** 的匿名访问
|
.requestMatchers("/api/public/*","/api/auth/login","/api/auth/register").permitAll()
|
||||||
.requestMatchers("/api/auth/login","/api/auth/register").permitAll()
|
|
||||||
.anyRequest().authenticated()
|
.anyRequest().authenticated()
|
||||||
)
|
)
|
||||||
// 禁用 CSRF,因为现代前后端分离项目通常使用 Token
|
// 禁用 CSRF,因为现代前后端分离项目通常使用 Token
|
||||||
@@ -75,8 +75,7 @@ public class SecurityConfig {
|
|||||||
|
|
||||||
CorsConfiguration configuration = new CorsConfiguration();
|
CorsConfiguration configuration = new CorsConfiguration();
|
||||||
configuration.setAllowedOrigins(Arrays.asList(
|
configuration.setAllowedOrigins(Arrays.asList(
|
||||||
"http://localhost:63342", // 你的前端运行的地址
|
"http://localhost:5173", // 其他可能的前端地址
|
||||||
"http://localhost:8080", // 其他可能的前端地址
|
|
||||||
"http://127.0.0.1:5500", // 另一个常见的本地开发地址
|
"http://127.0.0.1:5500", // 另一个常见的本地开发地址
|
||||||
"null" // 如果是从文件系统直接打开HTML文件,Origin 会是 "null"。仅用于开发!
|
"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.Autowired;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
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.context.SecurityContextHolder;
|
||||||
import org.springframework.security.core.userdetails.UserDetails;
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||||
@@ -17,6 +18,7 @@ import org.springframework.stereotype.Component;
|
|||||||
import org.springframework.web.filter.OncePerRequestFilter;
|
import org.springframework.web.filter.OncePerRequestFilter;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* JWT 登录授权过滤器
|
* JWT 登录授权过滤器
|
||||||
@@ -25,8 +27,6 @@ import java.io.IOException;
|
|||||||
@Slf4j
|
@Slf4j
|
||||||
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
|
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private UserDetailsService userDetailsService;
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private JwtTokenUtil jwtTokenUtil;
|
private JwtTokenUtil jwtTokenUtil;
|
||||||
@Value("${jwt.tokenHeader}")
|
@Value("${jwt.tokenHeader}")
|
||||||
@@ -38,10 +38,8 @@ public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
|
|||||||
protected void doFilterInternal(HttpServletRequest request,
|
protected void doFilterInternal(HttpServletRequest request,
|
||||||
HttpServletResponse response,
|
HttpServletResponse response,
|
||||||
FilterChain chain) throws ServletException, IOException {
|
FilterChain chain) throws ServletException, IOException {
|
||||||
log.info("JWT 登录授权过滤器");
|
|
||||||
|
|
||||||
String authHeader = request.getHeader(this.tokenHeader);
|
String authHeader = request.getHeader(this.tokenHeader);
|
||||||
log.info("authHeader: {}", authHeader);
|
|
||||||
if (authHeader != null && authHeader.startsWith(this.tokenHead)) {
|
if (authHeader != null && authHeader.startsWith(this.tokenHead)) {
|
||||||
String authToken = authHeader.substring(this.tokenHead.length()+1);
|
String authToken = authHeader.substring(this.tokenHead.length()+1);
|
||||||
log.info("authToken={}", authToken);
|
log.info("authToken={}", authToken);
|
||||||
@@ -49,11 +47,19 @@ public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
|
|||||||
log.info("username={}", username);
|
log.info("username={}", username);
|
||||||
// 如果 Token 中有用户名但上下文中没有,说明是首次登录
|
// 如果 Token 中有用户名但上下文中没有,说明是首次登录
|
||||||
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
|
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
|
||||||
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
|
|
||||||
// 验证 Token 是否有效
|
// 验证 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 =
|
UsernamePasswordAuthenticationToken authentication =
|
||||||
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
|
new UsernamePasswordAuthenticationToken(username, null, authorities);
|
||||||
|
request.setAttribute("currentUsername", username);
|
||||||
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
|
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
|
||||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint {
|
|||||||
response.setContentType("application/json");
|
response.setContentType("application/json");
|
||||||
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
|
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
|
||||||
|
|
||||||
ApiResult<Object> result = ApiResult.failed("未授权");
|
ApiResult<Object> result = ApiResult.failed(403,"权限不足");
|
||||||
|
|
||||||
String string = JSONObject.toJSONString(result);
|
String string = JSONObject.toJSONString(result);
|
||||||
response.getWriter().print(string);
|
response.getWriter().print(string);
|
||||||
|
|||||||
@@ -1,61 +1,50 @@
|
|||||||
package cn.nopj.chaos_api.controller;
|
package cn.nopj.chaos_api.controller;
|
||||||
|
|
||||||
import cn.nopj.chaos_api.dto.LoginRequest;
|
import cn.nopj.chaos_api.converter.UserConverter;
|
||||||
import cn.nopj.chaos_api.dto.RegisterRequest;
|
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.model.ApiResult;
|
||||||
import cn.nopj.chaos_api.service.AuthService;
|
import cn.nopj.chaos_api.service.AuthService;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.validation.annotation.Validated;
|
||||||
import org.springframework.security.authentication.BadCredentialsException;
|
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 认证管理
|
||||||
|
*
|
||||||
|
*/
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/auth")
|
@RequestMapping("/api/auth")
|
||||||
public class AuthController {
|
public class AuthController {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private AuthService authService;
|
private AuthService authService;
|
||||||
@Value("${jwt.tokenHead}")
|
|
||||||
private String tokenHead;
|
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private UserConverter userConverter;
|
||||||
/**
|
/**
|
||||||
* 注册
|
* 注册
|
||||||
*
|
*
|
||||||
* @param registerRequest 注册信息
|
* @param authRegisterRequest 注册信息
|
||||||
* @return 注册结果
|
* @return 注册结果
|
||||||
*/
|
*/
|
||||||
@PostMapping("/register")
|
@PostMapping("/register")
|
||||||
public ApiResult<?> register(@RequestBody RegisterRequest registerRequest) {
|
public ApiResult<?> register(@RequestBody @Validated AuthRegisterRequest authRegisterRequest) {
|
||||||
if (authService.register(registerRequest) != null) {
|
return ApiResult.success(authService.register(userConverter.convert(authRegisterRequest)));
|
||||||
return ApiResult.success("注册成功");
|
|
||||||
}
|
|
||||||
return ApiResult.failed("用户名已存在");
|
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* 登录
|
* 登录
|
||||||
*
|
*
|
||||||
* @param loginRequest 登录信息
|
* @param authLoginRequest 登录信息
|
||||||
* @return 登录结果
|
* @return 登录结果
|
||||||
*/
|
*/
|
||||||
@PostMapping("/login")
|
@PostMapping("/login")
|
||||||
public ApiResult<?> login(@RequestBody LoginRequest loginRequest) {
|
public ApiResult<AuthTokenResponse> login(@RequestBody @Validated AuthLoginRequest authLoginRequest) {
|
||||||
try {
|
return ApiResult.success(authService.login(authLoginRequest.getUsername(), authLoginRequest.getPassword()));
|
||||||
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());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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("上传文件失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,13 +1,65 @@
|
|||||||
package cn.nopj.chaos_api.controller;
|
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 lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
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
|
@Slf4j
|
||||||
@RestController
|
@RestController
|
||||||
|
@RequestMapping("/api/roles")
|
||||||
public class RoleController {
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,108 @@
|
|||||||
package cn.nopj.chaos_api.controller;
|
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 lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户管理
|
||||||
|
*
|
||||||
|
* @author nopj
|
||||||
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@RestController
|
@RestController
|
||||||
|
@RequestMapping("/api/users")
|
||||||
|
@RequiredArgsConstructor
|
||||||
public class UserController {
|
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;
|
package cn.nopj.chaos_api.service;
|
||||||
|
|
||||||
import cn.nopj.chaos_api.domain.entity.User;
|
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 {
|
public interface AuthService {
|
||||||
/**
|
/**
|
||||||
* 注册
|
* 注册
|
||||||
*/
|
*/
|
||||||
User register(RegisterRequest registerRequest);
|
User register(User user);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 登录
|
* 登录
|
||||||
* @return 生成的 JWT token
|
* @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;
|
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.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.mapper.UserMapper;
|
||||||
|
import cn.nopj.chaos_api.security.CustomUserDetails;
|
||||||
import cn.nopj.chaos_api.service.AuthService;
|
import cn.nopj.chaos_api.service.AuthService;
|
||||||
import cn.nopj.chaos_api.util.JwtTokenUtil;
|
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.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.security.authentication.AuthenticationManager;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
import org.springframework.security.authentication.*;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
import org.springframework.security.core.userdetails.UserDetails;
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
|
@Slf4j
|
||||||
public class AuthServiceImpl implements AuthService {
|
public class AuthServiceImpl implements AuthService {
|
||||||
|
|
||||||
|
|
||||||
@@ -29,37 +36,70 @@ public class AuthServiceImpl implements AuthService {
|
|||||||
private JwtTokenUtil jwtTokenUtil;
|
private JwtTokenUtil jwtTokenUtil;
|
||||||
@Autowired
|
@Autowired
|
||||||
private AuthenticationManager authenticationManager;
|
private AuthenticationManager authenticationManager;
|
||||||
|
@Value("${jwt.tokenHead}")
|
||||||
|
private String tokenHead;
|
||||||
|
@Value("${auth.def-role-code}")
|
||||||
|
private String defRoleCode;
|
||||||
|
@Autowired
|
||||||
|
private RoleMapper roleMapper;
|
||||||
|
|
||||||
@Override
|
@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 加密密码
|
// 使用 BCrypt 加密密码
|
||||||
user.setPassword(passwordEncoder.encode(registerRequest.getPassword()));
|
user.setPassword(passwordEncoder.encode(user.getPassword()));
|
||||||
userMapper.insert(user);
|
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;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String login(String username, String password) {
|
public AuthTokenResponse login(String username, String password) {
|
||||||
|
log.info("用户登录: {}", username);
|
||||||
|
// 尝试进行身份验证
|
||||||
|
try{
|
||||||
Authentication authentication = authenticationManager.authenticate(
|
Authentication authentication = authenticationManager.authenticate(
|
||||||
new UsernamePasswordAuthenticationToken(username, password)
|
new UsernamePasswordAuthenticationToken(username, password)
|
||||||
);
|
);
|
||||||
|
log.info("用户登录成功: {}", username);
|
||||||
|
// 将认证结果保存在 SecurityContextHolder 中
|
||||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||||
|
|
||||||
|
CustomUserDetails userDetails = (CustomUserDetails)authentication.getPrincipal();
|
||||||
// 获取用户详情
|
// 获取用户详情
|
||||||
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
|
|
||||||
if (!userDetails.isEnabled()) {
|
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;
|
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.domain.entity.User;
|
||||||
import cn.nopj.chaos_api.mapper.UserMapper;
|
import cn.nopj.chaos_api.mapper.UserMapper;
|
||||||
|
import cn.nopj.chaos_api.security.CustomUserDetails;
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.security.core.GrantedAuthority;
|
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.UserDetails;
|
||||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
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 org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
|
@Slf4j
|
||||||
public class UserDetailsServiceImpl implements UserDetailsService {
|
public class UserDetailsServiceImpl implements UserDetailsService {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
UserMapper userMapper;
|
UserMapper userMapper;
|
||||||
@Override
|
@Override
|
||||||
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
|
public CustomUserDetails loadUserByUsername(String username) {
|
||||||
// 1. 从数据库查询用户
|
// 1. 从数据库查询用户
|
||||||
User user = userMapper.selectOne(new QueryWrapper<User>().eq("username", username));
|
User user = userMapper.findUserWithRolesByUsername(username);
|
||||||
if (user == null) {
|
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 对象
|
// 4. 构建并返回 Spring Security 的 User 对象
|
||||||
return new org.springframework.security.core.userdetails.User(
|
return new CustomUserDetails(user);
|
||||||
user.getUsername(),
|
|
||||||
user.getPassword(),
|
|
||||||
user.getEnabled(),
|
|
||||||
user.getAccountNonExpired(),
|
|
||||||
user.getCredentialsNonExpired(),
|
|
||||||
user.getAccountNonLocked(),
|
|
||||||
grantedAuthorities
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,21 @@
|
|||||||
package cn.nopj.chaos_api.util;
|
package cn.nopj.chaos_api.util;
|
||||||
|
|
||||||
|
import cn.nopj.chaos_api.security.CustomUserDetails;
|
||||||
import com.auth0.jwt.JWT;
|
import com.auth0.jwt.JWT;
|
||||||
import com.auth0.jwt.JWTVerifier;
|
import com.auth0.jwt.JWTVerifier;
|
||||||
import com.auth0.jwt.algorithms.Algorithm;
|
import com.auth0.jwt.algorithms.Algorithm;
|
||||||
import com.auth0.jwt.interfaces.DecodedJWT;
|
import com.auth0.jwt.interfaces.DecodedJWT;
|
||||||
import jakarta.annotation.PostConstruct;
|
import jakarta.annotation.PostConstruct;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.security.core.userdetails.UserDetails;
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@@ -53,10 +57,9 @@ public class JwtTokenUtil {
|
|||||||
/**
|
/**
|
||||||
* 验证 Token 是否有效
|
* 验证 Token 是否有效
|
||||||
*/
|
*/
|
||||||
public boolean validateToken(String token, UserDetails userDetails) {
|
public boolean validateToken(String token) {
|
||||||
try {
|
try {
|
||||||
String username = getUsernameFromToken(token);
|
return decodeToken(token) != null;
|
||||||
return username != null && username.equals(userDetails.getUsername()) && !isTokenExpired(token);
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -65,14 +68,24 @@ public class JwtTokenUtil {
|
|||||||
/**
|
/**
|
||||||
* 生成 Token
|
* 生成 Token
|
||||||
*/
|
*/
|
||||||
public String generateToken(UserDetails userDetails) {
|
public String generateToken(CustomUserDetails userDetails) {
|
||||||
Date now = new Date();
|
Date now = new Date();
|
||||||
Date expiryDate = new Date(now.getTime() + expiration * 1000);
|
Date expiryDate = new Date(now.getTime() + expiration * 1000);
|
||||||
|
|
||||||
|
List<String> authorities = userDetails.getAuthorities()
|
||||||
|
.stream()
|
||||||
|
.map(GrantedAuthority::getAuthority)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
|
||||||
return JWT.create()
|
return JWT.create()
|
||||||
.withSubject(userDetails.getUsername())
|
.withSubject(userDetails.getUsername())
|
||||||
.withIssuedAt(now)
|
.withIssuedAt(now)
|
||||||
.withExpiresAt(expiryDate)
|
.withExpiresAt(expiryDate)
|
||||||
|
.withClaim("authorities", authorities)
|
||||||
|
.withClaim("userId", userDetails.getUserId())
|
||||||
|
.withClaim("nickname", userDetails.getNickname())
|
||||||
|
.withClaim("avatar", userDetails.getAvatar())
|
||||||
.sign(algorithm);
|
.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:
|
output:
|
||||||
ansi:
|
ansi:
|
||||||
enabled: always
|
enabled: always
|
||||||
|
profiles:
|
||||||
|
active: dev
|
||||||
|
|
||||||
mybatis-plus:
|
mybatis-plus:
|
||||||
mapper-locations: classpath*:/mapper/**/*.xml
|
mapper-locations: classpath*:/mapper/**/*.xml
|
||||||
@@ -29,14 +31,19 @@ mybatis-plus:
|
|||||||
global-config:
|
global-config:
|
||||||
db-config:
|
db-config:
|
||||||
id-type: auto
|
id-type: auto
|
||||||
|
logic-not-delete-value: "null"
|
||||||
|
logic-delete-value: "NOW()"
|
||||||
|
|
||||||
configuration:
|
configuration:
|
||||||
map-underscore-to-camel-case: true
|
map-underscore-to-camel-case: true
|
||||||
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
|
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
|
||||||
|
|
||||||
|
|
||||||
jwt:
|
jwt:
|
||||||
tokenHeader: Authorization
|
tokenHeader: Authorization
|
||||||
tokenHead: Chaos
|
tokenHead: Chaos
|
||||||
secret: zHANgcHao@1995!20250506
|
secret: zHANgcHao@1995!20250506
|
||||||
expiration: 604800
|
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 '系统管理-用户角色关联表';
|
||||||
|
|
||||||
20
pom.xml
20
pom.xml
@@ -119,6 +119,7 @@
|
|||||||
<artifactId>logback-classic</artifactId>
|
<artifactId>logback-classic</artifactId>
|
||||||
<version>1.5.18</version>
|
<version>1.5.18</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-amqp -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-amqp</artifactId>
|
<artifactId>spring-boot-starter-amqp</artifactId>
|
||||||
@@ -130,6 +131,25 @@
|
|||||||
<artifactId>spring-web</artifactId>
|
<artifactId>spring-web</artifactId>
|
||||||
<version>6.2.13</version>
|
<version>6.2.13</version>
|
||||||
</dependency>
|
</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>
|
</dependencies>
|
||||||
</dependencyManagement>
|
</dependencyManagement>
|
||||||
</project>
|
</project>
|
||||||
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