用户
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
package com.yupi.project;
|
||||
|
||||
import org.mybatis.spring.annotation.MapperScan;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
@MapperScan("com.yupi.project.mapper")
|
||||
public class MyApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(MyApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.yupi.project.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* 权限校验
|
||||
*
|
||||
* @author yupi
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface AuthCheck {
|
||||
|
||||
/**
|
||||
* 有任何一个角色
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
String[] anyRole() default "";
|
||||
|
||||
/**
|
||||
* 必须有某个角色
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
String mustRole() default "";
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
package com.yupi.project.aop;
|
||||
|
||||
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
|
||||
import com.yupi.project.annotation.AuthCheck;
|
||||
import com.yupi.project.common.ErrorCode;
|
||||
import com.yupi.project.exception.BusinessException;
|
||||
import com.yupi.project.model.entity.User;
|
||||
import com.yupi.project.model.enums.UserRoleEnum;
|
||||
import com.yupi.project.service.UserService;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.aspectj.lang.annotation.Around;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.context.request.RequestAttributes;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
||||
/**
|
||||
* 权限校验 AOP
|
||||
*
|
||||
* @author yupi
|
||||
*/
|
||||
@Aspect
|
||||
@Component
|
||||
public class AuthInterceptor {
|
||||
|
||||
@Resource
|
||||
private UserService userService;
|
||||
|
||||
/**
|
||||
* 执行拦截
|
||||
*
|
||||
* @param joinPoint
|
||||
* @param authCheck
|
||||
* @return
|
||||
*/
|
||||
@Around("@annotation(authCheck)")
|
||||
public Object doInterceptor(ProceedingJoinPoint joinPoint, AuthCheck authCheck) throws Throwable {
|
||||
List<String> anyRole = Arrays.stream(authCheck.anyRole()).filter(StringUtils::isNotBlank).collect(Collectors.toList());
|
||||
String mustRole = authCheck.mustRole();
|
||||
RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
|
||||
HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
|
||||
// 当前登录用户
|
||||
User user = userService.getCurrentUser(request);
|
||||
// 拥有任意权限即通过
|
||||
if (CollectionUtils.isNotEmpty(anyRole)) {
|
||||
String userRole = user.getRole();
|
||||
if (!anyRole.contains(userRole)) {
|
||||
throw new BusinessException(ErrorCode.NO_AUTH_ERROR);
|
||||
}
|
||||
}
|
||||
// 必须有所有权限才通过
|
||||
if (StringUtils.isNotBlank(mustRole)) {
|
||||
UserRoleEnum mustUserRoleEnum = UserRoleEnum.getEnumByValue(mustRole);
|
||||
if (mustUserRoleEnum == null) {
|
||||
throw new BusinessException(ErrorCode.NO_AUTH_ERROR);
|
||||
}
|
||||
String userRole = user.getRole();
|
||||
if (UserRoleEnum.ADMIN.equals(mustUserRoleEnum)) {
|
||||
if (!mustRole.equals(userRole)) {
|
||||
throw new BusinessException(ErrorCode.NO_AUTH_ERROR);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 通过权限校验,放行
|
||||
return joinPoint.proceed();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
package com.yupi.project.aop;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.aspectj.lang.annotation.Around;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StopWatch;
|
||||
import org.springframework.web.context.request.RequestAttributes;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* 请求响应日志 AOP
|
||||
*
|
||||
* @author yupi
|
||||
**/
|
||||
@Aspect
|
||||
@Component
|
||||
@Slf4j
|
||||
public class LogInterceptor {
|
||||
|
||||
/**
|
||||
* 执行拦截
|
||||
*/
|
||||
@Around("execution(* com.yupi.project.controller.*.*(..))")
|
||||
public Object doInterceptor(ProceedingJoinPoint point) throws Throwable {
|
||||
// 计时
|
||||
StopWatch stopWatch = new StopWatch();
|
||||
stopWatch.start();
|
||||
// 获取请求路径
|
||||
RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
|
||||
HttpServletRequest httpServletRequest = ((ServletRequestAttributes) requestAttributes).getRequest();
|
||||
// 生成请求唯一 id
|
||||
String requestId = UUID.randomUUID().toString();
|
||||
String url = httpServletRequest.getRequestURI();
|
||||
// 获取请求参数
|
||||
Object[] args = point.getArgs();
|
||||
String reqParam = "[" + StringUtils.join(args, ", ") + "]";
|
||||
// 输出请求日志
|
||||
log.info("request start,id: {}, path: {}, ip: {}, params: {}", requestId, url,
|
||||
httpServletRequest.getRemoteHost(), reqParam);
|
||||
// 执行原方法
|
||||
Object result = point.proceed();
|
||||
// 输出响应日志
|
||||
stopWatch.stop();
|
||||
long totalTimeMillis = stopWatch.getTotalTimeMillis();
|
||||
log.info("request end, id: {}, cost: {}ms", requestId, totalTimeMillis);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.yupi.project.common;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 通用返回类
|
||||
*
|
||||
* @param <T>
|
||||
* @author yupi
|
||||
*/
|
||||
@Data
|
||||
public class BaseResponse<T> implements Serializable {
|
||||
|
||||
private int code;
|
||||
|
||||
private T data;
|
||||
|
||||
private String message;
|
||||
|
||||
public BaseResponse(int code, T data, String message) {
|
||||
this.code = code;
|
||||
this.data = data;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public BaseResponse(int code, T data) {
|
||||
this(code, data, "");
|
||||
}
|
||||
|
||||
public BaseResponse(ErrorCode errorCode) {
|
||||
this(errorCode.getCode(), null, errorCode.getMessage());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.yupi.project.common;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 删除请求
|
||||
*
|
||||
* @author yupi
|
||||
*/
|
||||
@Data
|
||||
public class DeleteRequest implements Serializable {
|
||||
/**
|
||||
* id
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.yupi.project.common;
|
||||
|
||||
/**
|
||||
* 错误码
|
||||
*
|
||||
* @author yupi
|
||||
*/
|
||||
public enum ErrorCode {
|
||||
|
||||
SUCCESS(0, "ok"),
|
||||
PARAMS_ERROR(40000, "请求参数错误"),
|
||||
NOT_LOGIN_ERROR(40100, "未登录"),
|
||||
NO_AUTH_ERROR(40101, "无权限"),
|
||||
NOT_FOUND_ERROR(40400, "请求数据不存在"),
|
||||
FORBIDDEN_ERROR(40300, "禁止访问"),
|
||||
SYSTEM_ERROR(50000, "系统内部异常"),
|
||||
OPERATION_ERROR(50001, "操作失败");
|
||||
|
||||
/**
|
||||
* 状态码
|
||||
*/
|
||||
private final int code;
|
||||
|
||||
/**
|
||||
* 信息
|
||||
*/
|
||||
private final String message;
|
||||
|
||||
ErrorCode(int code, String message) {
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public int getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.yupi.project.common;
|
||||
|
||||
import com.yupi.project.constant.CommonConstant;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 分页请求
|
||||
*
|
||||
* @author yupi
|
||||
*/
|
||||
@Data
|
||||
public class PageRequest {
|
||||
|
||||
/**
|
||||
* 当前页号
|
||||
*/
|
||||
private long current = 1;
|
||||
|
||||
/**
|
||||
* 页面大小
|
||||
*/
|
||||
private long pageSize = 10;
|
||||
|
||||
/**
|
||||
* 排序字段
|
||||
*/
|
||||
private String sortField;
|
||||
|
||||
/**
|
||||
* 排序顺序(默认升序)
|
||||
*/
|
||||
private String sortOrder = CommonConstant.SORT_ORDER_ASC;
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package com.yupi.project.common;
|
||||
|
||||
/**
|
||||
* 返回工具类
|
||||
*
|
||||
* @author yupi
|
||||
*/
|
||||
public class ResultUtils {
|
||||
|
||||
/**
|
||||
* 成功
|
||||
*
|
||||
* @param data
|
||||
* @param <T>
|
||||
* @return
|
||||
*/
|
||||
public static <T> BaseResponse<T> success(T data) {
|
||||
return new BaseResponse<>(0, data, "ok");
|
||||
}
|
||||
|
||||
/**
|
||||
* 失败
|
||||
*
|
||||
* @param errorCode
|
||||
* @return
|
||||
*/
|
||||
public static BaseResponse error(ErrorCode errorCode) {
|
||||
return new BaseResponse<>(errorCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* 失败
|
||||
*
|
||||
* @param code
|
||||
* @param message
|
||||
* @return
|
||||
*/
|
||||
public static BaseResponse error(int code, String message) {
|
||||
return new BaseResponse(code, null, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 失败
|
||||
*
|
||||
* @param errorCode
|
||||
* @return
|
||||
*/
|
||||
public static BaseResponse error(ErrorCode errorCode, String message) {
|
||||
return new BaseResponse(errorCode.getCode(), null, message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.yupi.project.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import springfox.documentation.builders.ApiInfoBuilder;
|
||||
import springfox.documentation.builders.PathSelectors;
|
||||
import springfox.documentation.builders.RequestHandlerSelectors;
|
||||
import springfox.documentation.spi.DocumentationType;
|
||||
import springfox.documentation.spring.web.plugins.Docket;
|
||||
import springfox.documentation.swagger2.annotations.EnableSwagger2;
|
||||
|
||||
/**
|
||||
* Knife4j 接口文档配置
|
||||
* https://doc.xiaominfo.com/knife4j/documentation/get_start.html
|
||||
*
|
||||
* @author yupi
|
||||
*/
|
||||
@Configuration
|
||||
@EnableSwagger2
|
||||
@Profile("dev")
|
||||
public class Knife4jConfig {
|
||||
|
||||
@Bean
|
||||
public Docket defaultApi2() {
|
||||
return new Docket(DocumentationType.SWAGGER_2)
|
||||
.apiInfo(new ApiInfoBuilder()
|
||||
.title("project-backend")
|
||||
.description("project-backend")
|
||||
.version("1.0")
|
||||
.build())
|
||||
.select()
|
||||
// 指定 Controller 扫描包路径
|
||||
.apis(RequestHandlerSelectors.basePackage("com.yupi.project.controller"))
|
||||
.paths(PathSelectors.any())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package com.yupi.project.config;
|
||||
|
||||
import com.yupi.project.config.properties.MqttProperties;
|
||||
import com.yupi.project.mqtt.MqttCallbackHandler;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.eclipse.paho.client.mqttv3.MqttClient;
|
||||
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
|
||||
import org.eclipse.paho.client.mqttv3.MqttException;
|
||||
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.PreDestroy;
|
||||
import java.util.UUID;
|
||||
|
||||
@Slf4j
|
||||
@Configuration
|
||||
@RequiredArgsConstructor
|
||||
public class MqttConfig {
|
||||
|
||||
private final MqttProperties mqttProperties;
|
||||
private final MqttCallbackHandler mqttCallbackHandler;
|
||||
|
||||
@Autowired // Autowire the MqttClient bean defined below
|
||||
private MqttClient mqttClient;
|
||||
|
||||
@Bean
|
||||
public MqttConnectOptions mqttConnectOptions() {
|
||||
MqttConnectOptions options = new MqttConnectOptions();
|
||||
if (StringUtils.hasText(mqttProperties.getUsername())) {
|
||||
options.setUserName(mqttProperties.getUsername());
|
||||
}
|
||||
if (StringUtils.hasText(mqttProperties.getPassword())) {
|
||||
options.setPassword(mqttProperties.getPassword().toCharArray());
|
||||
}
|
||||
options.setAutomaticReconnect(true); // Enable automatic reconnect
|
||||
options.setCleanSession(true); // Start with a clean session
|
||||
options.setConnectionTimeout(mqttProperties.getConnectionTimeout());
|
||||
options.setKeepAliveInterval(mqttProperties.getKeepAliveInterval());
|
||||
// options.setWill(...) // Configure Last Will and Testament if needed
|
||||
return options;
|
||||
}
|
||||
|
||||
@Bean // Bean method name will be the bean name by default: "mqttClientBean"
|
||||
public MqttClient mqttClientBean(MqttConnectOptions mqttConnectOptions) throws MqttException {
|
||||
String clientId = mqttProperties.getClientIdPrefix() + UUID.randomUUID().toString().replace("-", "");
|
||||
MqttClient client = new MqttClient(mqttProperties.getBrokerUrl(), clientId, new MemoryPersistence());
|
||||
client.setCallback(mqttCallbackHandler);
|
||||
// Pass the client instance to the handler so it can subscribe on connectComplete
|
||||
mqttCallbackHandler.setMqttClient(client);
|
||||
return client;
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void connect() {
|
||||
try {
|
||||
// Use the autowired mqttClient field
|
||||
if (this.mqttClient != null && !this.mqttClient.isConnected()) {
|
||||
log.info("Attempting to connect to MQTT broker: {} with client ID: {}", mqttProperties.getBrokerUrl(), this.mqttClient.getClientId());
|
||||
this.mqttClient.connect(mqttConnectOptions()); // mqttConnectOptions() provides the bean
|
||||
// Subscription logic is now in MqttCallbackHandler.connectComplete
|
||||
} else if (this.mqttClient == null) {
|
||||
log.error("MqttClient (autowired) is null, cannot connect.");
|
||||
}
|
||||
} catch (MqttException e) {
|
||||
log.error("Error connecting to MQTT broker: ", e);
|
||||
// Consider retry logic or application failure based on requirements
|
||||
}
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
public void disconnect() {
|
||||
try {
|
||||
// Use the autowired mqttClient field
|
||||
if (this.mqttClient != null && this.mqttClient.isConnected()) {
|
||||
log.info("Disconnecting from MQTT broker: {}", this.mqttClient.getServerURI());
|
||||
this.mqttClient.disconnect();
|
||||
log.info("Successfully disconnected from MQTT broker.");
|
||||
}
|
||||
if (this.mqttClient != null) {
|
||||
this.mqttClient.close();
|
||||
}
|
||||
} catch (MqttException e) {
|
||||
log.error("Error disconnecting from MQTT broker: ", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.yupi.project.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;
|
||||
|
||||
/**
|
||||
* MyBatis Plus 配置
|
||||
*
|
||||
* @author yupi
|
||||
*/
|
||||
@Configuration
|
||||
@MapperScan("com.yupi.project.mapper")
|
||||
public class MyBatisPlusConfig {
|
||||
|
||||
/**
|
||||
* 拦截器配置
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Bean
|
||||
public MybatisPlusInterceptor mybatisPlusInterceptor() {
|
||||
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
|
||||
// 分页插件
|
||||
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
|
||||
return interceptor;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package com.yupi.project.config;
|
||||
|
||||
import com.yupi.project.model.enums.UserRoleEnum;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.cors.CorsConfigurationSource;
|
||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* 安全相关配置 (集成CORS)
|
||||
*/
|
||||
@Configuration
|
||||
@EnableWebSecurity // 启用Spring Security的Web安全支持
|
||||
public class SecurityConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Bean
|
||||
public PasswordEncoder passwordEncoder() {
|
||||
// 使用 BCrypt 进行密码加密
|
||||
return new BCryptPasswordEncoder();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http
|
||||
// 集成CORS配置
|
||||
.cors()
|
||||
.and()
|
||||
// 明确禁用CSRF保护
|
||||
.csrf().disable()
|
||||
.authorizeRequests(
|
||||
// 允许对登录和注册接口的匿名访问 (路径不带 /api 前缀)
|
||||
authorize -> authorize
|
||||
.antMatchers("/user/login", "/user/register", "/error").permitAll()
|
||||
.antMatchers("/user/current").authenticated()
|
||||
// 使用 hasAuthority 确保匹配 UserRoleEnum.ADMIN.getValue() 即 "admin"
|
||||
.antMatchers("/user/list").hasAuthority(UserRoleEnum.ADMIN.getValue())
|
||||
.antMatchers(HttpMethod.DELETE, "/user/delete/**").hasAuthority(UserRoleEnum.ADMIN.getValue())
|
||||
.antMatchers(HttpMethod.POST, "/user/admin/add").hasAuthority(UserRoleEnum.ADMIN.getValue())
|
||||
.antMatchers(HttpMethod.PUT, "/user/admin/update").hasAuthority(UserRoleEnum.ADMIN.getValue())
|
||||
// 其他所有请求都需要认证
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
// 禁用默认的表单登录页面
|
||||
.formLogin().disable()
|
||||
// 禁用HTTP Basic认证
|
||||
.httpBasic().disable();
|
||||
}
|
||||
|
||||
@Bean
|
||||
CorsConfigurationSource corsConfigurationSource() {
|
||||
CorsConfiguration configuration = new CorsConfiguration();
|
||||
// TODO: 在生产环境请显式指定允许的源,而不是 "*"
|
||||
configuration.setAllowedOriginPatterns(Arrays.asList("*")); // 允许所有源模式
|
||||
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS"));
|
||||
configuration.setAllowedHeaders(Arrays.asList("*")); // 允许所有头
|
||||
configuration.setAllowCredentials(true); // 允许发送 Cookie
|
||||
configuration.setMaxAge(3600L); // 预检请求的有效期
|
||||
// configuration.setExposedHeaders(Arrays.asList("YourCustomHeader")); // 如果需要暴露自定义头
|
||||
|
||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||
source.registerCorsConfiguration("/**", configuration); // 对所有路径应用配置
|
||||
return source;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.yupi.project.config.properties;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Data
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "mqtt")
|
||||
public class MqttProperties {
|
||||
|
||||
private String brokerUrl;
|
||||
private String username;
|
||||
private String password;
|
||||
private String clientIdPrefix;
|
||||
private int defaultQos;
|
||||
private int connectionTimeout;
|
||||
private int keepAliveInterval;
|
||||
private String commandTopicBase;
|
||||
private String statusTopicBase;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.yupi.project.constant;
|
||||
|
||||
/**
|
||||
* 通用常量
|
||||
*
|
||||
* @author yupi
|
||||
*/
|
||||
public interface CommonConstant {
|
||||
|
||||
/**
|
||||
* 升序
|
||||
*/
|
||||
String SORT_ORDER_ASC = "ascend";
|
||||
|
||||
/**
|
||||
* 降序
|
||||
*/
|
||||
String SORT_ORDER_DESC = " descend";
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.yupi.project.constant;
|
||||
|
||||
/**
|
||||
* 用户常量
|
||||
*
|
||||
* @author yupi
|
||||
*/
|
||||
public interface UserConstant {
|
||||
|
||||
/**
|
||||
* 用户登录态键
|
||||
*/
|
||||
String USER_LOGIN_STATE = "userLoginState";
|
||||
|
||||
/**
|
||||
* 系统用户 id(虚拟用户)
|
||||
*/
|
||||
long SYSTEM_USER_ID = 0;
|
||||
|
||||
// region 权限
|
||||
|
||||
/**
|
||||
* 默认角色
|
||||
*/
|
||||
String DEFAULT_ROLE = "user";
|
||||
|
||||
/**
|
||||
* 管理员角色
|
||||
*/
|
||||
String ADMIN_ROLE = "admin";
|
||||
|
||||
// endregion
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
package com.yupi.project.controller;
|
||||
|
||||
import com.yupi.project.common.BaseResponse;
|
||||
import com.yupi.project.common.ErrorCode;
|
||||
import com.yupi.project.common.ResultUtils;
|
||||
import com.yupi.project.exception.BusinessException;
|
||||
import com.yupi.project.model.dto.user.UserLoginRequest;
|
||||
import com.yupi.project.model.dto.user.UserRegisterRequest;
|
||||
import com.yupi.project.model.entity.User;
|
||||
import com.yupi.project.model.enums.UserRoleEnum;
|
||||
import com.yupi.project.service.UserService;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 用户接口
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/user")
|
||||
public class UserController {
|
||||
|
||||
@Resource
|
||||
private UserService userService;
|
||||
|
||||
// --- 登录注册相关 --- //
|
||||
|
||||
@PostMapping("/register")
|
||||
public BaseResponse<Long> userRegister(@RequestBody UserRegisterRequest userRegisterRequest) {
|
||||
if (userRegisterRequest == null) {
|
||||
throw new BusinessException(ErrorCode.PARAMS_ERROR);
|
||||
}
|
||||
String username = userRegisterRequest.getUsername();
|
||||
String password = userRegisterRequest.getPassword();
|
||||
String checkPassword = userRegisterRequest.getCheckPassword();
|
||||
if (StringUtils.isAnyBlank(username, password, checkPassword)) {
|
||||
throw new BusinessException(ErrorCode.PARAMS_ERROR, "参数不能为空");
|
||||
}
|
||||
long result = userService.userRegister(username, password, checkPassword);
|
||||
return ResultUtils.success(result);
|
||||
}
|
||||
|
||||
@PostMapping("/login")
|
||||
public BaseResponse<User> userLogin(@RequestBody UserLoginRequest userLoginRequest, HttpServletRequest request) {
|
||||
if (userLoginRequest == null) {
|
||||
throw new BusinessException(ErrorCode.PARAMS_ERROR);
|
||||
}
|
||||
String username = userLoginRequest.getUsername();
|
||||
String password = userLoginRequest.getPassword();
|
||||
if (StringUtils.isAnyBlank(username, password)) {
|
||||
throw new BusinessException(ErrorCode.PARAMS_ERROR, "参数不能为空");
|
||||
}
|
||||
User user = userService.userLogin(username, password, request);
|
||||
return ResultUtils.success(user);
|
||||
}
|
||||
|
||||
@PostMapping("/logout")
|
||||
public BaseResponse<Boolean> userLogout(HttpServletRequest request) {
|
||||
if (request == null) {
|
||||
throw new BusinessException(ErrorCode.PARAMS_ERROR);
|
||||
}
|
||||
boolean result = userService.userLogout(request);
|
||||
return ResultUtils.success(result);
|
||||
}
|
||||
|
||||
@GetMapping("/current")
|
||||
public BaseResponse<User> getCurrentUser(HttpServletRequest request) {
|
||||
User currentUser = userService.getCurrentUser(request);
|
||||
// getCurrentUser 内部已处理未登录情况,并返回脱敏用户
|
||||
return ResultUtils.success(currentUser);
|
||||
}
|
||||
|
||||
// --- 管理员功能 --- //
|
||||
|
||||
@GetMapping("/list")
|
||||
public BaseResponse<List<User>> listUsers(HttpServletRequest request) {
|
||||
// 权限校验 (在 SecurityConfig 中配置,这里可以简化或移除,但保留显式检查作为双重保险)
|
||||
User currentUser = userService.getCurrentUser(request); // 获取当前用户以检查角色
|
||||
if (currentUser == null || !UserRoleEnum.ADMIN.getValue().equals(currentUser.getRole())) {
|
||||
throw new BusinessException(ErrorCode.NO_AUTH_ERROR, "无管理员权限");
|
||||
}
|
||||
List<User> userList = userService.listUsers(); // 调用新的 service 方法,它已经处理了脱敏
|
||||
return ResultUtils.success(userList);
|
||||
}
|
||||
|
||||
// 新增管理员删除用户接口
|
||||
@DeleteMapping("/delete/{userId}")
|
||||
public BaseResponse<Boolean> adminDeleteUser(@PathVariable Long userId, HttpServletRequest request) {
|
||||
// 权限校验 (虽然 SecurityConfig 会拦截,但 Controller 层再校验一次更安全)
|
||||
User currentUser = userService.getCurrentUser(request);
|
||||
if (currentUser == null || !UserRoleEnum.ADMIN.getValue().equals(currentUser.getRole())) {
|
||||
throw new BusinessException(ErrorCode.NO_AUTH_ERROR, "无管理员权限");
|
||||
}
|
||||
|
||||
if (userId == null || userId <= 0) {
|
||||
throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户ID参数错误");
|
||||
}
|
||||
|
||||
// 防止管理员误删自己 (可选逻辑)
|
||||
if (currentUser.getId().equals(userId)) {
|
||||
throw new BusinessException(ErrorCode.OPERATION_ERROR, "不能删除当前登录的管理员账户");
|
||||
}
|
||||
|
||||
boolean result = userService.adminDeleteUser(userId);
|
||||
return ResultUtils.success(result);
|
||||
}
|
||||
|
||||
// 新增管理员添加用户接口
|
||||
@PostMapping("/admin/add")
|
||||
public BaseResponse<Long> adminAddUser(@RequestBody com.yupi.project.model.dto.user.UserAdminAddRequest addRequest, HttpServletRequest request) {
|
||||
// 权限校验
|
||||
User currentUser = userService.getCurrentUser(request);
|
||||
if (currentUser == null || !UserRoleEnum.ADMIN.getValue().equals(currentUser.getRole())) {
|
||||
throw new BusinessException(ErrorCode.NO_AUTH_ERROR, "无管理员权限");
|
||||
}
|
||||
|
||||
if (addRequest == null) {
|
||||
throw new BusinessException(ErrorCode.PARAMS_ERROR, "请求参数不能为空");
|
||||
}
|
||||
|
||||
long newUserId = userService.adminAddUser(addRequest);
|
||||
return ResultUtils.success(newUserId);
|
||||
}
|
||||
|
||||
// 新增管理员更新用户接口
|
||||
@PutMapping("/admin/update")
|
||||
public BaseResponse<Boolean> adminUpdateUser(@RequestBody com.yupi.project.model.dto.user.UserAdminUpdateRequest updateRequest, HttpServletRequest request) {
|
||||
// 权限校验
|
||||
User currentUser = userService.getCurrentUser(request);
|
||||
if (currentUser == null || !UserRoleEnum.ADMIN.getValue().equals(currentUser.getRole())) {
|
||||
throw new BusinessException(ErrorCode.NO_AUTH_ERROR, "无管理员权限");
|
||||
}
|
||||
|
||||
if (updateRequest == null || updateRequest.getId() == null || updateRequest.getId() <=0) {
|
||||
throw new BusinessException(ErrorCode.PARAMS_ERROR, "请求参数错误,必须提供用户ID");
|
||||
}
|
||||
|
||||
// 防止管理员通过此接口修改自己的角色把自己改成普通用户,或修改关键的ADMIN账户 (可选)
|
||||
// 如果要更新的用户是当前登录的管理员,并且尝试修改角色为非admin,则拒绝
|
||||
if (currentUser.getId().equals(updateRequest.getId()) &&
|
||||
StringUtils.isNotBlank(updateRequest.getRole()) &&
|
||||
!UserRoleEnum.ADMIN.getValue().equals(updateRequest.getRole())) {
|
||||
throw new BusinessException(ErrorCode.OPERATION_ERROR, "管理员不能将自己的角色修改为非管理员");
|
||||
}
|
||||
|
||||
boolean result = userService.adminUpdateUser(updateRequest);
|
||||
return ResultUtils.success(result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.yupi.project.exception;
|
||||
|
||||
import com.yupi.project.common.ErrorCode;
|
||||
|
||||
/**
|
||||
* 自定义异常类
|
||||
*
|
||||
* @author yupi
|
||||
*/
|
||||
public class BusinessException extends RuntimeException {
|
||||
|
||||
private final int code;
|
||||
|
||||
public BusinessException(int code, String message) {
|
||||
super(message);
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public BusinessException(ErrorCode errorCode) {
|
||||
super(errorCode.getMessage());
|
||||
this.code = errorCode.getCode();
|
||||
}
|
||||
|
||||
public BusinessException(ErrorCode errorCode, String message) {
|
||||
super(message);
|
||||
this.code = errorCode.getCode();
|
||||
}
|
||||
|
||||
public int getCode() {
|
||||
return code;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.yupi.project.exception;
|
||||
|
||||
import com.yupi.project.common.BaseResponse;
|
||||
import com.yupi.project.common.ErrorCode;
|
||||
import com.yupi.project.common.ResultUtils;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
|
||||
/**
|
||||
* 全局异常处理器
|
||||
*
|
||||
* @author yupi
|
||||
*/
|
||||
@RestControllerAdvice
|
||||
@Slf4j
|
||||
public class GlobalExceptionHandler {
|
||||
|
||||
@ExceptionHandler(BusinessException.class)
|
||||
public BaseResponse<?> businessExceptionHandler(BusinessException e) {
|
||||
log.error("businessException: " + e.getMessage(), e);
|
||||
return ResultUtils.error(e.getCode(), e.getMessage());
|
||||
}
|
||||
|
||||
@ExceptionHandler(RuntimeException.class)
|
||||
public BaseResponse<?> runtimeExceptionHandler(RuntimeException e) {
|
||||
log.error("runtimeException", e);
|
||||
return ResultUtils.error(ErrorCode.SYSTEM_ERROR, e.getMessage());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.yupi.project.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.yupi.project.model.entity.User;
|
||||
|
||||
/**
|
||||
* @description 针对表【user(用户表)】的数据库操作Mapper
|
||||
* @createDate 2023-11-24 10:00:00
|
||||
* @Entity com.yupi.project.model.entity.User
|
||||
*/
|
||||
public interface UserMapper extends BaseMapper<User> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.yupi.project.model.dto.user;
|
||||
|
||||
import lombok.Data;
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 管理员添加用户请求体
|
||||
*/
|
||||
@Data
|
||||
public class UserAdminAddRequest implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 用户名
|
||||
*/
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* 密码
|
||||
*/
|
||||
private String password;
|
||||
|
||||
/**
|
||||
* 用户角色 (例如 "user", "admin")
|
||||
*/
|
||||
private String role;
|
||||
|
||||
/**
|
||||
* 初始余额 (可选)
|
||||
*/
|
||||
private BigDecimal balance;
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.yupi.project.model.dto.user;
|
||||
|
||||
import lombok.Data;
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 管理员更新用户请求体
|
||||
*/
|
||||
@Data
|
||||
public class UserAdminUpdateRequest implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 用户ID (必须)
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 用户名 (可选,如果提供则尝试更新)
|
||||
*/
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* 新密码 (可选,如果提供则重置密码)
|
||||
*/
|
||||
private String password;
|
||||
|
||||
/**
|
||||
* 用户角色 (可选,如果提供则尝试更新)
|
||||
*/
|
||||
private String role;
|
||||
|
||||
/**
|
||||
* 余额 (可选,如果提供则尝试更新)
|
||||
*/
|
||||
private BigDecimal balance;
|
||||
|
||||
// isDeleted 不应由管理员直接通过此接口修改,应通过删除接口
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.yupi.project.model.dto.user;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 用户登录请求体
|
||||
*/
|
||||
@Data
|
||||
public class UserLoginRequest implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 3191241716373120793L;
|
||||
|
||||
private String username;
|
||||
|
||||
private String password;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.yupi.project.model.dto.user;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 用户注册请求体
|
||||
*/
|
||||
@Data
|
||||
public class UserRegisterRequest implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 3191241716373120793L;
|
||||
|
||||
private String username;
|
||||
|
||||
private String password;
|
||||
|
||||
private String checkPassword;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.yupi.project.model.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 用户表
|
||||
*/
|
||||
@TableName(value ="user")
|
||||
@Data
|
||||
public class User implements Serializable {
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 用户名
|
||||
*/
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* 密码 (加密存储)
|
||||
*/
|
||||
private String password;
|
||||
|
||||
/**
|
||||
* 角色 (user/admin)
|
||||
*/
|
||||
private String role;
|
||||
|
||||
/**
|
||||
* 账户余额
|
||||
*/
|
||||
private BigDecimal balance;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private Date createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
private Date updateTime;
|
||||
|
||||
/**
|
||||
* 逻辑删除标志 (0:未删, 1:已删)
|
||||
*/
|
||||
@TableLogic
|
||||
private Integer isDeleted;
|
||||
|
||||
@TableField(exist = false)
|
||||
private static final long serialVersionUID = 1L;
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package com.yupi.project.model.enums;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
|
||||
/**
|
||||
* 用户角色枚举
|
||||
*
|
||||
* @author yupi
|
||||
*/
|
||||
public enum UserRoleEnum {
|
||||
|
||||
USER("user", "用户"),
|
||||
ADMIN("admin", "管理员"),
|
||||
BAN("ban", "被封号");
|
||||
|
||||
private final String value;
|
||||
|
||||
private final String text;
|
||||
|
||||
UserRoleEnum(String value, String text) {
|
||||
this.value = value;
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取值列表
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static List<String> getValues() {
|
||||
return Arrays.stream(values()).map(item -> item.value).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 value 获取枚举
|
||||
*
|
||||
* @param value
|
||||
* @return
|
||||
*/
|
||||
public static UserRoleEnum getEnumByValue(String value) {
|
||||
if (ObjectUtils.isEmpty(value)) {
|
||||
return null;
|
||||
}
|
||||
for (UserRoleEnum anEnum : UserRoleEnum.values()) {
|
||||
if (anEnum.value.equals(value)) {
|
||||
return anEnum;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package com.yupi.project.mqtt;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
|
||||
import org.eclipse.paho.client.mqttv3.MqttCallbackExtended;
|
||||
import org.eclipse.paho.client.mqttv3.MqttClient;
|
||||
import org.eclipse.paho.client.mqttv3.MqttMessage;
|
||||
import org.springframework.stereotype.Component;
|
||||
// import com.yupi.project.service.MqttMessageHandler; // Will be uncommented and used later
|
||||
import com.yupi.project.config.properties.MqttProperties;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class MqttCallbackHandler implements MqttCallbackExtended {
|
||||
|
||||
// private final MqttMessageHandler mqttMessageHandler; // Will be uncommented and used later
|
||||
private final MqttProperties mqttProperties;
|
||||
private MqttClient mqttClient; // Setter needed or passed in constructor/method
|
||||
|
||||
public void setMqttClient(MqttClient mqttClient) {
|
||||
this.mqttClient = mqttClient;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connectComplete(boolean reconnect, String serverURI) {
|
||||
log.info("MQTT connection {} to broker: {}", reconnect ? "re-established" : "established", serverURI);
|
||||
// Subscribe to the status topic upon connection/reconnection
|
||||
try {
|
||||
if (mqttClient != null && mqttClient.isConnected()) {
|
||||
String statusTopic = mqttProperties.getStatusTopicBase() + "/+"; // Subscribe to all robot statuses
|
||||
mqttClient.subscribe(statusTopic, mqttProperties.getDefaultQos());
|
||||
log.info("Subscribed to MQTT topic: {}", statusTopic);
|
||||
} else {
|
||||
log.warn("MQTT client not available or not connected, cannot subscribe to topic.");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("Error subscribing to MQTT topic after connection complete: ", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connectionLost(Throwable cause) {
|
||||
log.error("MQTT connection lost!", cause);
|
||||
// Automatic reconnect is handled by MqttConnectOptions
|
||||
}
|
||||
|
||||
@Override
|
||||
public void messageArrived(String topic, MqttMessage message) throws Exception {
|
||||
String payload = new String(message.getPayload());
|
||||
log.debug("MQTT message arrived. Topic: {}, QoS: {}, Payload:\n{}", topic, message.getQos(), payload);
|
||||
|
||||
// TODO: Implement application-level authentication/validation of the message payload here or in MqttMessageHandler
|
||||
|
||||
// try {
|
||||
// mqttMessageHandler.handleStatusUpdate(topic, payload); // Will be uncommented and used later
|
||||
// } catch (Exception e) {
|
||||
// log.error("Error handling MQTT message for topic {}: ", topic, e);
|
||||
// }
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deliveryComplete(IMqttDeliveryToken token) {
|
||||
// This callback is invoked when a message published by this client has been successfully delivered
|
||||
// Used for QoS 1 and 2. For QoS 0, it's called after the message has been handed to the network.
|
||||
try {
|
||||
if (token.isComplete() && token.getMessage() != null) {
|
||||
log.trace("MQTT message delivery complete. Message ID: {}, Payload: {}", token.getMessageId(), new String(token.getMessage().getPayload()));
|
||||
} else if (token.isComplete()){
|
||||
log.trace("MQTT message delivery complete. Message ID: {}", token.getMessageId());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("Error in MQTT deliveryComplete callback: ", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
package com.yupi.project.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.yupi.project.model.entity.User;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @description 针对表【user(用户表)】的数据库操作Service
|
||||
* @createDate 2023-11-24 10:05:00
|
||||
*/
|
||||
public interface UserService extends IService<User> {
|
||||
|
||||
/**
|
||||
* 用户注册
|
||||
*
|
||||
* @param username 用户名
|
||||
* @param password 密码
|
||||
* @param checkPassword 校验密码
|
||||
* @return 新用户 id
|
||||
*/
|
||||
long userRegister(String username, String password, String checkPassword);
|
||||
|
||||
/**
|
||||
* 用户登录
|
||||
*
|
||||
* @param username 用户名
|
||||
* @param password 密码
|
||||
* @param request HttpServletRequest 用于操作 session
|
||||
* @return 脱敏后的用户信息
|
||||
*/
|
||||
User userLogin(String username, String password, HttpServletRequest request);
|
||||
|
||||
/**
|
||||
* 获取当前登录用户
|
||||
*
|
||||
* @param request HttpServletRequest
|
||||
* @return 当前登录用户,未登录则返回 null
|
||||
*/
|
||||
User getCurrentUser(HttpServletRequest request);
|
||||
|
||||
/**
|
||||
* 用户注销
|
||||
*
|
||||
* @param request HttpServletRequest
|
||||
* @return 是否成功
|
||||
*/
|
||||
boolean userLogout(HttpServletRequest request);
|
||||
|
||||
/**
|
||||
* 获取脱敏的用户信息
|
||||
*
|
||||
* @param originUser 原始用户信息
|
||||
* @return 脱敏后的用户信息
|
||||
*/
|
||||
User getSafetyUser(User originUser);
|
||||
|
||||
/**
|
||||
* 扣减用户余额 (需要保证线程安全和数据一致性)
|
||||
* @param userId 用户ID
|
||||
* @param amount 扣减金额 (正数)
|
||||
* @return 操作是否成功
|
||||
*/
|
||||
boolean deductBalance(Long userId, BigDecimal amount);
|
||||
|
||||
/**
|
||||
* 增加用户余额 (需要保证线程安全和数据一致性)
|
||||
* @param userId 用户ID
|
||||
* @param amount 增加金额 (正数)
|
||||
* @return 操作是否成功
|
||||
*/
|
||||
boolean addBalance(Long userId, BigDecimal amount);
|
||||
|
||||
/**
|
||||
* 获取用户列表 (仅管理员)
|
||||
*
|
||||
* @return 脱敏后的用户列表
|
||||
*/
|
||||
List<User> listUsers();
|
||||
|
||||
/**
|
||||
* 管理员删除用户 (逻辑删除)
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @return 操作是否成功
|
||||
*/
|
||||
boolean adminDeleteUser(Long userId);
|
||||
|
||||
/**
|
||||
* 管理员添加新用户
|
||||
*
|
||||
* @param addRequest 包含新用户信息的请求体
|
||||
* @return 新用户的ID
|
||||
*/
|
||||
long adminAddUser(com.yupi.project.model.dto.user.UserAdminAddRequest addRequest);
|
||||
|
||||
/**
|
||||
* 管理员更新用户信息
|
||||
*
|
||||
* @param updateRequest 包含待更新用户信息的请求体
|
||||
* @return 操作是否成功
|
||||
*/
|
||||
boolean adminUpdateUser(com.yupi.project.model.dto.user.UserAdminUpdateRequest updateRequest);
|
||||
}
|
||||
@@ -0,0 +1,392 @@
|
||||
package com.yupi.project.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.yupi.project.common.ErrorCode;
|
||||
import com.yupi.project.constant.UserConstant;
|
||||
import com.yupi.project.exception.BusinessException;
|
||||
import com.yupi.project.mapper.UserMapper;
|
||||
import com.yupi.project.model.entity.User;
|
||||
import com.yupi.project.service.UserService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* 用户服务实现类
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
public class UserServiceImpl extends ServiceImpl<UserMapper, User>
|
||||
implements UserService {
|
||||
|
||||
@Resource
|
||||
private UserMapper userMapper;
|
||||
|
||||
@Resource
|
||||
private PasswordEncoder passwordEncoder;
|
||||
|
||||
// 用户名校验正则:允许字母、数字、下划线,长度4到16位
|
||||
private static final String USERNAME_PATTERN = "^[a-zA-Z0-9_]{4,16}$";
|
||||
// 密码校验正则:至少包含字母和数字,长度至少6位
|
||||
private static final String PASSWORD_PATTERN = "^(?=.*[A-Za-z])(?=.*\\d)[A-Za-z\\d]{6,}$";
|
||||
|
||||
@Override
|
||||
public long userRegister(String username, String password, String checkPassword) {
|
||||
// 1. 参数校验
|
||||
if (StringUtils.isAnyBlank(username, password, checkPassword)) {
|
||||
throw new BusinessException(ErrorCode.PARAMS_ERROR, "参数不能为空");
|
||||
}
|
||||
if (!Pattern.matches(USERNAME_PATTERN, username)) {
|
||||
throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户名不符合规范(4-16位字母、数字、下划线)");
|
||||
}
|
||||
if (password.length() < 6) { // 使用更宽松的校验,正则校验留给前端
|
||||
throw new BusinessException(ErrorCode.PARAMS_ERROR, "密码长度不能小于6位");
|
||||
}
|
||||
if (!password.equals(checkPassword)) {
|
||||
throw new BusinessException(ErrorCode.PARAMS_ERROR, "两次输入的密码不一致");
|
||||
}
|
||||
|
||||
synchronized (username.intern()) {
|
||||
// 2. 检查用户名是否已存在
|
||||
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq("username", username);
|
||||
long count = this.count(queryWrapper);
|
||||
if (count > 0) {
|
||||
throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户名已存在");
|
||||
}
|
||||
|
||||
// 3. 密码加密
|
||||
String encodedPassword = passwordEncoder.encode(password);
|
||||
|
||||
// 4. 创建用户
|
||||
User userToSave = new User();
|
||||
userToSave.setUsername(username);
|
||||
userToSave.setPassword(encodedPassword);
|
||||
userToSave.setRole(UserConstant.DEFAULT_ROLE); // 默认角色 'user'
|
||||
userToSave.setBalance(BigDecimal.ZERO); // 初始余额为0
|
||||
boolean saveResult = this.save(userToSave);
|
||||
if (!saveResult) {
|
||||
throw new BusinessException(ErrorCode.SYSTEM_ERROR, "注册失败,数据库错误");
|
||||
}
|
||||
return userToSave.getId();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public User userLogin(String username, String password, HttpServletRequest request) {
|
||||
// 1. 参数校验
|
||||
if (StringUtils.isAnyBlank(username, password)) {
|
||||
throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户名或密码不能为空");
|
||||
}
|
||||
if (!Pattern.matches(USERNAME_PATTERN, username)) {
|
||||
throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户名格式错误");
|
||||
}
|
||||
|
||||
// 2. 查询用户
|
||||
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq("username", username);
|
||||
User user = this.getOne(queryWrapper);
|
||||
|
||||
// 3. 校验密码 和 用户存在性
|
||||
if (user == null || !passwordEncoder.matches(password, user.getPassword())) {
|
||||
throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户名或密码错误");
|
||||
}
|
||||
|
||||
// 4. 用户脱敏
|
||||
User safetyUser = getSafetyUser(user);
|
||||
|
||||
// 5. 创建 Authentication 对象并设置到 SecurityContext
|
||||
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
|
||||
if (StringUtils.isNotBlank(user.getRole())) { // 确保角色不为空
|
||||
authorities.add(new SimpleGrantedAuthority(user.getRole()));
|
||||
}
|
||||
|
||||
Authentication authentication = new UsernamePasswordAuthenticationToken(safetyUser, null, authorities);
|
||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||
|
||||
return safetyUser;
|
||||
}
|
||||
|
||||
@Override
|
||||
public User getCurrentUser(HttpServletRequest request) {
|
||||
// 优先从 SecurityContextHolder 获取认证信息
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
if (authentication != null && authentication.isAuthenticated() && !(authentication.getPrincipal() instanceof String && authentication.getPrincipal().equals("anonymousUser"))) {
|
||||
Object principal = authentication.getPrincipal();
|
||||
if (principal instanceof User) {
|
||||
return (User) principal; // principal 已经是 safetyUser
|
||||
} else if (principal instanceof org.springframework.security.core.userdetails.User) {
|
||||
// 如果 principal 是 Spring Security 的 User (不太可能在这里,因为我们设置的是 safetyUser)
|
||||
// 需要转换或重新查询
|
||||
// For now, assume it's our User object based on login logic
|
||||
}
|
||||
}
|
||||
|
||||
// 如果 SecurityContextHolder 中没有,尝试从 session (旧逻辑,作为后备或移除)
|
||||
Object userObj = request.getSession().getAttribute(UserConstant.USER_LOGIN_STATE);
|
||||
if (userObj instanceof User) {
|
||||
// 最好在这里也验证一下数据库中的用户状态,或者确保session中的信息足够可信
|
||||
return (User) userObj;
|
||||
}
|
||||
|
||||
throw new BusinessException(ErrorCode.NOT_LOGIN_ERROR);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean userLogout(HttpServletRequest request) {
|
||||
SecurityContextHolder.clearContext(); // 清除安全上下文
|
||||
if (request.getSession(false) != null) { // 获取session但不创建新的
|
||||
request.getSession(false).invalidate(); // 使session无效
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public User getSafetyUser(User originUser) {
|
||||
if (originUser == null) {
|
||||
return null;
|
||||
}
|
||||
User safetyUser = new User();
|
||||
BeanUtils.copyProperties(originUser, safetyUser, "password");
|
||||
return safetyUser;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public boolean deductBalance(Long userId, BigDecimal amount) {
|
||||
if (userId == null || userId <= 0 || amount == null || amount.compareTo(BigDecimal.ZERO) <= 0) {
|
||||
throw new BusinessException(ErrorCode.PARAMS_ERROR, "参数错误");
|
||||
}
|
||||
boolean updateResult = this.update()
|
||||
.setSql("balance = balance - " + amount.doubleValue())
|
||||
.eq("id", userId)
|
||||
.ge("balance", amount)
|
||||
.update();
|
||||
|
||||
if (!updateResult) {
|
||||
User user = this.getById(userId);
|
||||
if (user == null || user.getIsDeleted() == 1) {
|
||||
throw new BusinessException(ErrorCode.NOT_FOUND_ERROR, "用户不存在");
|
||||
} else if (user.getBalance().compareTo(amount) < 0) {
|
||||
throw new BusinessException(ErrorCode.OPERATION_ERROR, "余额不足");
|
||||
} else {
|
||||
log.warn("Deduct balance failed due to concurrent update for userId: {}", userId);
|
||||
throw new BusinessException(ErrorCode.OPERATION_ERROR, "扣款失败,请重试");
|
||||
}
|
||||
}
|
||||
log.info("Deducted {} from balance for user {}", amount, userId);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public boolean addBalance(Long userId, BigDecimal amount) {
|
||||
if (userId == null || userId <= 0 || amount == null || amount.compareTo(BigDecimal.ZERO) <= 0) {
|
||||
throw new BusinessException(ErrorCode.PARAMS_ERROR, "参数错误");
|
||||
}
|
||||
boolean updateResult = this.update()
|
||||
.setSql("balance = balance + " + amount.doubleValue())
|
||||
.eq("id", userId)
|
||||
.update();
|
||||
|
||||
if (!updateResult) {
|
||||
User user = this.getById(userId);
|
||||
if (user == null || user.getIsDeleted() == 1) {
|
||||
throw new BusinessException(ErrorCode.NOT_FOUND_ERROR, "用户不存在");
|
||||
}
|
||||
log.error("Add balance failed unexpectedly for userId: {}", userId);
|
||||
throw new BusinessException(ErrorCode.SYSTEM_ERROR, "充值失败,请稍后重试");
|
||||
}
|
||||
log.info("Added {} to balance for user {}", amount, userId);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<User> listUsers() {
|
||||
List<User> userList = this.list(new QueryWrapper<User>().eq("isDeleted", 0));
|
||||
return userList.stream().map(this::getSafetyUser).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
// 新增管理员删除用户实现
|
||||
@Override
|
||||
public boolean adminDeleteUser(Long userId) {
|
||||
if (userId == null || userId <= 0) {
|
||||
throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户ID参数错误");
|
||||
}
|
||||
User user = this.getById(userId);
|
||||
if (user == null || user.getIsDeleted() == 1) {
|
||||
throw new BusinessException(ErrorCode.NOT_FOUND_ERROR, "用户不存在或已被删除");
|
||||
}
|
||||
boolean result = this.removeById(userId);
|
||||
if (!result) {
|
||||
log.warn("Attempted to delete user that might already be deleted or does not exist, userId: {}", userId);
|
||||
User checkUser = this.getById(userId);
|
||||
if(checkUser == null || checkUser.getIsDeleted() == 1){
|
||||
return true;
|
||||
}
|
||||
throw new BusinessException(ErrorCode.SYSTEM_ERROR, "删除用户失败,数据库操作异常");
|
||||
}
|
||||
log.info("User with id: {} logically deleted by admin.", userId);
|
||||
return true;
|
||||
}
|
||||
|
||||
// 新增管理员添加用户实现
|
||||
@Override
|
||||
@Transactional // 确保操作的原子性
|
||||
public long adminAddUser(com.yupi.project.model.dto.user.UserAdminAddRequest addRequest) {
|
||||
String username = addRequest.getUsername();
|
||||
String password = addRequest.getPassword();
|
||||
String role = addRequest.getRole();
|
||||
BigDecimal balance = addRequest.getBalance();
|
||||
|
||||
// 1. 参数校验
|
||||
if (StringUtils.isAnyBlank(username, password, role)) {
|
||||
throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户名、密码和角色不能为空");
|
||||
}
|
||||
if (!Pattern.matches(USERNAME_PATTERN, username)) {
|
||||
throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户名不符合规范(4-16位字母、数字、下划线)");
|
||||
}
|
||||
if (password.length() < 6) {
|
||||
throw new BusinessException(ErrorCode.PARAMS_ERROR, "密码长度不能小于6位");
|
||||
}
|
||||
// 校验角色是否合法 (例如:必须是 UserRoleEnum 中的值)
|
||||
try {
|
||||
com.yupi.project.model.enums.UserRoleEnum.valueOf(role.toUpperCase());
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new BusinessException(ErrorCode.PARAMS_ERROR, "无效的用户角色");
|
||||
}
|
||||
if (balance != null && balance.compareTo(BigDecimal.ZERO) < 0) {
|
||||
throw new BusinessException(ErrorCode.PARAMS_ERROR, "初始余额不能为负数");
|
||||
}
|
||||
|
||||
synchronized (username.intern()) {
|
||||
// 2. 检查用户名是否已存在
|
||||
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq("username", username);
|
||||
if (this.count(queryWrapper) > 0) {
|
||||
throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户名已存在");
|
||||
}
|
||||
|
||||
// 3. 密码加密
|
||||
String encodedPassword = passwordEncoder.encode(password);
|
||||
|
||||
// 4. 创建用户
|
||||
User newUser = new User();
|
||||
newUser.setUsername(username);
|
||||
newUser.setPassword(encodedPassword);
|
||||
newUser.setRole(role); // 直接使用管理员指定的角色
|
||||
newUser.setBalance(balance != null ? balance : BigDecimal.ZERO); // 如果未提供余额,默认为0
|
||||
|
||||
boolean saveResult = this.save(newUser);
|
||||
if (!saveResult) {
|
||||
throw new BusinessException(ErrorCode.SYSTEM_ERROR, "添加用户失败,数据库错误");
|
||||
}
|
||||
log.info("Admin added new user: {}, ID: {}, Role: {}", username, newUser.getId(), role);
|
||||
return newUser.getId();
|
||||
}
|
||||
}
|
||||
|
||||
// 新增管理员更新用户实现
|
||||
@Override
|
||||
@Transactional
|
||||
public boolean adminUpdateUser(com.yupi.project.model.dto.user.UserAdminUpdateRequest updateRequest) {
|
||||
Long userId = updateRequest.getId();
|
||||
if (userId == null || userId <= 0) {
|
||||
throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户ID不能为空");
|
||||
}
|
||||
|
||||
User existingUser = this.getById(userId);
|
||||
if (existingUser == null || existingUser.getIsDeleted() == 1) {
|
||||
throw new BusinessException(ErrorCode.NOT_FOUND_ERROR, "用户不存在或已被删除");
|
||||
}
|
||||
|
||||
boolean needsUpdate = false;
|
||||
User userToUpdate = new User();
|
||||
userToUpdate.setId(userId);
|
||||
|
||||
// 更新用户名 (如果提供)
|
||||
if (StringUtils.isNotBlank(updateRequest.getUsername())) {
|
||||
String newUsername = updateRequest.getUsername();
|
||||
if (!Pattern.matches(USERNAME_PATTERN, newUsername)) {
|
||||
throw new BusinessException(ErrorCode.PARAMS_ERROR, "新用户名不符合规范");
|
||||
}
|
||||
// 检查新用户名是否与现有其他用户冲突
|
||||
if (!existingUser.getUsername().equals(newUsername)) {
|
||||
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq("username", newUsername).ne("id", userId);
|
||||
if (this.count(queryWrapper) > 0) {
|
||||
throw new BusinessException(ErrorCode.PARAMS_ERROR, "新用户名已被其他用户使用");
|
||||
}
|
||||
userToUpdate.setUsername(newUsername);
|
||||
needsUpdate = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 更新密码 (如果提供)
|
||||
if (StringUtils.isNotBlank(updateRequest.getPassword())) {
|
||||
String newPassword = updateRequest.getPassword();
|
||||
if (newPassword.length() < 6) {
|
||||
throw new BusinessException(ErrorCode.PARAMS_ERROR, "新密码长度不能小于6位");
|
||||
}
|
||||
userToUpdate.setPassword(passwordEncoder.encode(newPassword));
|
||||
needsUpdate = true;
|
||||
}
|
||||
|
||||
// 更新角色 (如果提供)
|
||||
if (StringUtils.isNotBlank(updateRequest.getRole())) {
|
||||
String newRole = updateRequest.getRole();
|
||||
try {
|
||||
com.yupi.project.model.enums.UserRoleEnum.valueOf(newRole.toUpperCase());
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new BusinessException(ErrorCode.PARAMS_ERROR, "无效的新用户角色");
|
||||
}
|
||||
userToUpdate.setRole(newRole);
|
||||
needsUpdate = true;
|
||||
}
|
||||
|
||||
// 更新余额 (如果提供)
|
||||
if (updateRequest.getBalance() != null) {
|
||||
BigDecimal newBalance = updateRequest.getBalance();
|
||||
if (newBalance.compareTo(BigDecimal.ZERO) < 0) {
|
||||
throw new BusinessException(ErrorCode.PARAMS_ERROR, "余额不能为负数");
|
||||
}
|
||||
userToUpdate.setBalance(newBalance);
|
||||
needsUpdate = true;
|
||||
}
|
||||
|
||||
if (!needsUpdate) {
|
||||
log.info("No fields to update for user ID: {}", userId);
|
||||
return true; // 没有字段需要更新,也视为成功
|
||||
}
|
||||
|
||||
boolean updateResult = this.updateById(userToUpdate);
|
||||
if (!updateResult) {
|
||||
throw new BusinessException(ErrorCode.SYSTEM_ERROR, "更新用户信息失败,数据库操作异常");
|
||||
}
|
||||
log.info("User ID: {} updated by admin. Fields updated: username={}, role={}, balance={}, password_changed={}",
|
||||
userId,
|
||||
userToUpdate.getUsername() != null,
|
||||
userToUpdate.getRole() != null,
|
||||
userToUpdate.getBalance() != null,
|
||||
userToUpdate.getPassword() != null
|
||||
);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
spring:
|
||||
datasource:
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
url: jdbc:mysql://localhost:3306/my_db
|
||||
username: root
|
||||
password: 123456
|
||||
62
springboot-init-main/src/main/resources/application.yml
Normal file
62
springboot-init-main/src/main/resources/application.yml
Normal file
@@ -0,0 +1,62 @@
|
||||
spring:
|
||||
application:
|
||||
name: mqtt-charging-system
|
||||
# DataSource Config
|
||||
datasource:
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
url: jdbc:mysql://yuyun-us1.stormrain.cn:3306/mqtt_power?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
|
||||
username: root
|
||||
password: mysql_a4MQ4P
|
||||
mvc:
|
||||
pathmatch:
|
||||
matching-strategy: ANT_PATH_MATCHER
|
||||
# session 失效时间(秒)
|
||||
session:
|
||||
timeout: 86400
|
||||
server:
|
||||
port: 7529
|
||||
servlet:
|
||||
context-path: /api
|
||||
session:
|
||||
timeout: 86400 # 设置session的过期时间,单位为秒,这里设置为1天
|
||||
mybatis-plus:
|
||||
configuration:
|
||||
map-underscore-to-camel-case: true
|
||||
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
|
||||
global-config:
|
||||
db-config:
|
||||
logic-delete-field: isDelete # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
|
||||
logic-delete-value: 1 # 逻辑已删除值(默认为 1)
|
||||
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
|
||||
|
||||
# Logging configuration
|
||||
logging:
|
||||
level:
|
||||
# Set root logger level (e.g., INFO, WARN, ERROR, DEBUG)
|
||||
root: INFO
|
||||
# Set specific package levels
|
||||
com.yupi.project: DEBUG # Example: Set your project's base package to DEBUG
|
||||
org.springframework.web: INFO # Set Spring Web logging level
|
||||
org.springframework.security: DEBUG # Enable Spring Security DEBUG logging
|
||||
org.mybatis: INFO # Set MyBatis logging level
|
||||
# ... other specific loggers
|
||||
#file:
|
||||
#name: logs/application.log # Log file name
|
||||
#path: ./logs # Log file path
|
||||
#pattern:
|
||||
#console: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"
|
||||
#file: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"
|
||||
|
||||
# ===================================================================
|
||||
# MQTT Configurations
|
||||
# ===================================================================
|
||||
mqtt:
|
||||
broker-url: tcp://broker.emqx.io:1883
|
||||
username: # Public broker, no credentials specified for connection
|
||||
password: # Public broker, no credentials specified for connection
|
||||
client-id-prefix: backend-yupi-mqtt-power- # Unique client ID prefix for our project
|
||||
default-qos: 1 # Default Quality of Service (0, 1, 2)
|
||||
connection-timeout: 30 # Connection timeout in seconds
|
||||
keep-alive-interval: 60 # Keep alive interval in seconds
|
||||
command-topic-base: yupi_mqtt_power_project/robot/command # Prefixed base topic for sending commands
|
||||
status-topic-base: yupi_mqtt_power_project/robot/status # Prefixed base topic for receiving status
|
||||
1
springboot-init-main/src/main/resources/banner.txt
Normal file
1
springboot-init-main/src/main/resources/banner.txt
Normal file
@@ -0,0 +1 @@
|
||||
我的项目 by 程序员鱼皮 https://github.com/liyupi
|
||||
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.yupi.project.mapper.UserMapper">
|
||||
|
||||
<resultMap id="BaseResultMap" type="com.yupi.project.model.entity.User">
|
||||
<id property="id" column="id" jdbcType="BIGINT"/>
|
||||
<result property="userName" column="userName" jdbcType="VARCHAR"/>
|
||||
<result property="userAccount" column="userAccount" jdbcType="VARCHAR"/>
|
||||
<result property="userAvatar" column="userAvatar" jdbcType="VARCHAR"/>
|
||||
<result property="gender" column="gender" jdbcType="TINYINT"/>
|
||||
<result property="userRole" column="userRole" jdbcType="VARCHAR"/>
|
||||
<result property="userPassword" column="userPassword" jdbcType="VARCHAR"/>
|
||||
<result property="createTime" column="createTime" jdbcType="TIMESTAMP"/>
|
||||
<result property="updateTime" column="updateTime" jdbcType="TIMESTAMP"/>
|
||||
<result property="isDelete" column="isDelete" jdbcType="TINYINT"/>
|
||||
</resultMap>
|
||||
|
||||
<sql id="Base_Column_List">
|
||||
id,userName,userAccount,
|
||||
userAvatar,gender,userRole,
|
||||
userPassword,createTime,updateTime,
|
||||
isDelete
|
||||
</sql>
|
||||
</mapper>
|
||||
Reference in New Issue
Block a user