浏览代码

feate: 单点登录

jackzhou 1 月之前
父节点
当前提交
de24095037
共有 16 个文件被更改,包括 378 次插入107 次删除
  1. 9 1
      snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/third/controller/AuthThirdController.java
  2. 8 0
      snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/third/entity/AuthThirdUser.java
  3. 6 1
      snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/third/enums/AuthThirdPlatformEnum.java
  4. 173 0
      snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/third/request/JfcloudCustomAuthRequest.java
  5. 31 0
      snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/third/request/config/JfcloudAuthSource.java
  6. 14 0
      snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/third/request/model/JfcloudAuthUser.java
  7. 69 61
      snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/third/service/impl/AuthThirdServiceImpl.java
  8. 2 0
      snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/core/alarm/service/delay/DeviceAlertDelayController.java
  9. 0 2
      snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/core/alarm/service/messagepush/impl/SMSPushService.java
  10. 1 1
      snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/core/config/JfcloudColdChainConstants.java
  11. 5 9
      snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/modular/airnow/config/MqttConfig.java
  12. 7 19
      snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/modular/airnow/service/impl/AirNowServiceImpl.java
  13. 15 4
      snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/modular/monitortarget/controller/MonitorTargetController.java
  14. 7 1
      snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/modular/monitortarget/service/MonitorTargetService.java
  15. 24 7
      snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/modular/monitortarget/service/impl/MonitorTargetServiceImpl.java
  16. 7 1
      snowy-plugin/snowy-plugin-dev/src/main/java/vip/xiaonuo/dev/modular/config/enums/DevConfigCategoryEnum.java

+ 9 - 1
snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/third/controller/AuthThirdController.java

@@ -43,7 +43,7 @@ public class AuthThirdController {
     @Resource
     private AuthThirdService authThirdService;
 
-    /**
+        /**
      * 第三方登录页面渲染
      *
      * @author xuyuxiang
@@ -54,6 +54,14 @@ public class AuthThirdController {
     public CommonResult<AuthThirdRenderResult> render(@Valid AuthThirdRenderParam authThirdRenderParam) {
         return CommonResult.data(authThirdService.render(authThirdRenderParam));
     }
+//    @Operation(summary = "第三方登录页面渲染")
+//    @GetMapping("/auth/third/render")
+//    public void render(@Valid AuthThirdRenderParam param, HttpServletResponse response) throws IOException {
+//        AuthThirdRenderResult render = authThirdService.render(param);
+//        String authorizeUrl = render.getAuthorizeUrl();
+//        response.sendRedirect(authorizeUrl);
+//    }
+
 
     /**
      * 第三方登录授权回调

+ 8 - 0
snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/third/entity/AuthThirdUser.java

@@ -70,4 +70,12 @@ public class AuthThirdUser extends CommonEntity {
     @Schema(description = "扩展信息")
     @TableField(insertStrategy = FieldStrategy.IGNORED, updateStrategy = FieldStrategy.IGNORED)
     private String extJson;
+
+    /** 手机 */
+    @Schema(description = "手机")
+    private String phone;
+
+    /** 邮箱 */
+    @Schema(description = "邮箱")
+    private String email;
 }

+ 6 - 1
snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/third/enums/AuthThirdPlatformEnum.java

@@ -29,6 +29,11 @@ public enum AuthThirdPlatformEnum {
      */
     GITEE("GITEE"),
 
+    /**
+     * OAUTH:中南医院
+     */
+    OAUTH("OAUTH"),
+
     /**
      * WECHAT
      */
@@ -41,7 +46,7 @@ public enum AuthThirdPlatformEnum {
     }
 
     public static void validate(String value) {
-        boolean flag = GITEE.getValue().equals(value) || WECHAT.getValue().equals(value);
+        boolean flag = GITEE.getValue().equals(value) || WECHAT.getValue().equals(value) || OAUTH.getValue().equals(value);
         if(!flag) {
             throw new CommonException("不支持的第三方平台:{}", value);
         }

+ 173 - 0
snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/third/request/JfcloudCustomAuthRequest.java

@@ -0,0 +1,173 @@
+package vip.xiaonuo.auth.modular.third.request;
+
+import cn.hutool.http.HttpRequest;
+import cn.hutool.http.HttpResponse;
+import cn.hutool.http.HttpUtil;
+import cn.hutool.json.JSONObject;
+import cn.hutool.json.JSONUtil;
+import lombok.extern.slf4j.Slf4j;
+import me.zhyd.oauth.config.AuthConfig;
+import me.zhyd.oauth.config.AuthSource;
+import me.zhyd.oauth.enums.AuthResponseStatus;
+import me.zhyd.oauth.exception.AuthException;
+import me.zhyd.oauth.model.AuthCallback;
+import me.zhyd.oauth.model.AuthResponse;
+import me.zhyd.oauth.model.AuthToken;
+import me.zhyd.oauth.model.AuthUser;
+import me.zhyd.oauth.request.AuthDefaultRequest;
+import me.zhyd.oauth.utils.AuthStateUtils;
+import me.zhyd.oauth.utils.UrlBuilder;
+import vip.xiaonuo.auth.modular.third.request.model.JfcloudAuthUser;
+
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 自定义认证请求(基于 Hutool HttpUtil 实现)
+ * 支持标准 OAuth2 协议的统一身份认证中心登录:
+ * - 授权URL:/oauth2Authorize
+ * - 获取token:/oauth2/token
+ * - 获取用户信息:/oauth2/userinfo
+ */
+@Slf4j
+public class JfcloudCustomAuthRequest extends AuthDefaultRequest {
+
+
+    public JfcloudCustomAuthRequest(AuthConfig config, AuthSource source) {
+        super(config, source);
+    }
+
+    @Override
+    public AuthResponse login(AuthCallback authCallback) {
+        AuthToken token = this.getAccessToken(authCallback);
+        AuthUser user = this.getUserInfo(token);
+        return AuthResponse.builder().code(AuthResponseStatus.SUCCESS.getCode()).data(user).build();
+    }
+
+
+    /**
+     * 构建授权URL,引导用户跳转登录
+     */
+    @Override
+    public String authorize(String state) {
+        String authorizeUrl = UrlBuilder.fromBaseUrl(source.authorize()).queryParam("response_type", "code").queryParam("scope", "userinfo").queryParam("client_id", config.getClientId()).queryParam("redirect_uri", config.getRedirectUri()).queryParam("state", state != null ? state : AuthStateUtils.createState()).build();
+        log.info("[JfcloudCustomAuthRequest] 构建授权URL: {}", authorizeUrl);
+        return authorizeUrl;
+    }
+
+    /**
+     * 根据授权码换取 AccessToken
+     */
+    @Override
+    protected AuthToken getAccessToken(AuthCallback authCallback) {
+        String tokenUrl = source.accessToken();
+
+        Map<String, Object> params = new HashMap<>();
+        params.put("grant_type", "authorization_code");
+        params.put("code", authCallback.getCode());
+        params.put("client_id", config.getClientId());
+        params.put("client_secret", config.getClientSecret());
+        params.put("redirect_uri", config.getRedirectUri());
+
+        log.info("[JfcloudCustomAuthRequest] 请求AccessToken URL={} 参数={}", tokenUrl, params);
+
+        HttpResponse response = HttpRequest.post(tokenUrl).header("Content-Type", "application/json").body(JSONUtil.toJsonStr(params)).charset(StandardCharsets.UTF_8).timeout(5000).execute();
+
+        String body = response.body();
+        log.info("[JfcloudCustomAuthRequest] AccessToken响应: {}", body);
+
+        if (response.getStatus() != 200) {
+            throw new AuthException("请求AccessToken失败,HTTP状态码:" + response.getStatus());
+        }
+
+        JSONObject json = JSONUtil.parseObj(body);
+        if (!json.getBool("success", false)) {
+            throw new AuthException("获取Token失败:" + json.getStr("message", "未知错误"));
+        }
+
+        JSONObject result = json.getJSONObject("result");
+        if (result == null) {
+            throw new AuthException("Token响应格式异常:" + body);
+        }
+
+        return AuthToken.builder().accessToken(result.getStr("accessToken")).expireIn(5000).openId(result.getStr("openId")).tokenType("Bearer").scope("userinfo").build();
+    }
+
+    /**
+     * 根据Token获取用户信息
+     * Header: Znyy-Token: accessToken
+     */
+    @Override
+    protected AuthUser getUserInfo(AuthToken authToken) {
+        String userInfoUrl = source.userInfo();
+        log.info("[JfcloudCustomAuthRequest] 获取用户信息 URL={} Token={}", userInfoUrl, authToken.getAccessToken());
+
+        HttpResponse response = HttpRequest.get(userInfoUrl).header("Znyy-Token", authToken.getAccessToken()).charset(StandardCharsets.UTF_8).timeout(5000).execute();
+
+        String body = response.body();
+        log.info("[JfcloudCustomAuthRequest] 用户信息响应: {}", body);
+
+        if (response.getStatus() != 200) {
+            throw new AuthException("请求用户信息失败,HTTP状态码:" + response.getStatus());
+        }
+
+        JSONObject json = JSONUtil.parseObj(body);
+        if (!json.getBool("success", false)) {
+            throw new AuthException("获取用户信息失败:" + json.getStr("message", "未知错误"));
+        }
+        JSONObject result = json.getJSONObject("result");
+        if (result == null) {
+            throw new AuthException("用户信息响应格式异常:" + body);
+        }
+        JfcloudAuthUser user = new JfcloudAuthUser();
+        user.setUuid(result.getStr("openId"));
+        user.setUsername(result.getStr("workNo"));
+        user.setNickname(result.getStr("name"));
+        user.setPhone(result.getStr("phone"));
+        user.setSource(source.toString());
+        user.setToken(authToken);
+        return user;
+    }
+
+    /**
+     * 刷新 Token
+     */
+    @Override
+    public AuthResponse refresh(AuthToken authToken) {
+        String refreshUrl = source.refresh();
+
+        Map<String, Object> params = new HashMap<>();
+        params.put("client_id", config.getClientId());
+        params.put("client_secret", config.getClientSecret());
+        params.put("grant_type", "refresh_token");
+        params.put("refresh_token", authToken.getRefreshToken());
+
+        log.info("[JfcloudCustomAuthRequest] 刷新Token URL={} 参数={}", refreshUrl, params);
+
+        String body = HttpUtil.post(refreshUrl, JSONUtil.toJsonStr(params));
+        log.info("[JfcloudCustomAuthRequest] 刷新Token响应: {}", body);
+
+        JSONObject json = JSONUtil.parseObj(body);
+        if (!json.getBool("success", false)) {
+            return AuthResponse.builder().code(AuthResponseStatus.FAILURE.getCode()).msg(json.getStr("message", "刷新Token失败")).build();
+        }
+
+        JSONObject result = json.getJSONObject("result");
+        AuthToken newToken = AuthToken.builder().accessToken(result.getStr("accessToken")).expireIn(5000).openId(result.getStr("openId")).tokenType("Bearer").build();
+
+        return AuthResponse.builder().code(AuthResponseStatus.SUCCESS.getCode()).data(newToken).build();
+    }
+
+//    public String buildAuthorizeUrl(String clientId, String redirectUri, String state) {
+//        String baseUrl = "https://yjzxsysxt.znhospital.cn/oauth2Authorize";
+//        return StrUtil.format(
+//                "{}?response_type=code&scope=userinfo&client_id={}&redirect_uri={}&state={}",
+//                baseUrl,
+//                URLUtil.encode(clientId),
+//                URLUtil.encode(redirectUri),
+//                URLUtil.encode(state)
+//        );
+//    }
+
+}

+ 31 - 0
snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/third/request/config/JfcloudAuthSource.java

@@ -0,0 +1,31 @@
+package vip.xiaonuo.auth.modular.third.request.config;
+
+import me.zhyd.oauth.config.AuthSource;
+import me.zhyd.oauth.request.AuthDefaultRequest;
+import me.zhyd.oauth.request.AuthGithubRequest;
+
+public enum JfcloudAuthSource implements AuthSource {
+    ZHONG_NAN {
+        @Override
+        public String authorize() {
+            return "https://yjzxsysxt.znhospital.cn/oauth2Authorize";
+        }
+
+        @Override
+        public String accessToken() {
+            return "https://yjzxsysxt.znhospital.cn/oauth2/token";
+        }
+
+        @Override
+        public String userInfo() {
+            return "https://yjzxsysxt.znhospital.cn/oauth2/userinfo";
+        }
+
+        public Class<? extends AuthDefaultRequest> getTargetClass() {
+            return AuthGithubRequest.class;
+        }
+    };
+
+    private JfcloudAuthSource() {
+    }
+}

+ 14 - 0
snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/third/request/model/JfcloudAuthUser.java

@@ -0,0 +1,14 @@
+package vip.xiaonuo.auth.modular.third.request.model;
+
+import lombok.*;
+import me.zhyd.oauth.model.AuthUser;
+
+@Getter
+@Setter
+@NoArgsConstructor
+@AllArgsConstructor
+@ToString(callSuper = true)
+public class JfcloudAuthUser extends AuthUser {
+    /** 手机号 */
+    private String phone;
+}

+ 69 - 61
snowy-plugin/snowy-plugin-auth/src/main/java/vip/xiaonuo/auth/modular/third/service/impl/AuthThirdServiceImpl.java

@@ -43,6 +43,9 @@ import vip.xiaonuo.auth.modular.third.mapper.AuthThirdMapper;
 import vip.xiaonuo.auth.modular.third.param.AuthThirdCallbackParam;
 import vip.xiaonuo.auth.modular.third.param.AuthThirdRenderParam;
 import vip.xiaonuo.auth.modular.third.param.AuthThirdUserPageParam;
+import vip.xiaonuo.auth.modular.third.request.JfcloudCustomAuthRequest;
+import vip.xiaonuo.auth.modular.third.request.config.JfcloudAuthSource;
+import vip.xiaonuo.auth.modular.third.request.model.JfcloudAuthUser;
 import vip.xiaonuo.auth.modular.third.result.AuthThirdRenderResult;
 import vip.xiaonuo.auth.modular.third.service.AuthThirdService;
 import vip.xiaonuo.common.enums.CommonSortOrderEnum;
@@ -67,6 +70,12 @@ public class AuthThirdServiceImpl extends ServiceImpl<AuthThirdMapper, AuthThird
     private static final String SNOWY_THIRD_WECHAT_CLIENT_SECRET_KEY = "SNOWY_THIRD_WECHAT_CLIENT_SECRET";
     private static final String SNOWY_THIRD_WECHAT_REDIRECT_URL_KEY = "SNOWY_THIRD_WECHAT_REDIRECT_URL";
 
+    // OAUTH_第三方客户端信息
+    private static final String OAUTH_THIRD_CLIENT_ID_KEY = "OAUTH_THIRD_CLIENT_ID";
+    private static final String OAUTH_THIRD_CLIENT_SECRET_KEY = "OAUTH_THIRD_CLIENT_SECRET";
+    private static final String OAUTH_THIRD_REDIRECT_URL_KEY = "OAUTH_THIRD_REDIRECT_URL";
+
+
     @Resource
     private DevConfigApi devConfigApi;
 
@@ -81,22 +90,16 @@ public class AuthThirdServiceImpl extends ServiceImpl<AuthThirdMapper, AuthThird
 
     @Override
     public AuthThirdRenderResult render(AuthThirdRenderParam authThirdRenderParam) {
-
         // 获取请求
         AuthRequest authRequest = this.getAuthRequest(authThirdRenderParam.getPlatform());
-
         // 获取状态
         String state = AuthStateUtils.createState();
-
         // 构造授权地址
         String authorizeUrl = authRequest.authorize(state);
-
         // 构造结果
         AuthThirdRenderResult authThirdRenderResult = new AuthThirdRenderResult();
-
         // 返回授权地址
         authThirdRenderResult.setAuthorizeUrl(authorizeUrl);
-
         // 返回状态码
         authThirdRenderResult.setState(state);
         return authThirdRenderResult;
@@ -106,40 +109,36 @@ public class AuthThirdServiceImpl extends ServiceImpl<AuthThirdMapper, AuthThird
     @Transactional(rollbackFor = Exception.class)
     @Override
     public String callback(AuthThirdCallbackParam authThirdCallbackParam, AuthCallback authCallback) {
-
-        // 获取请求
-        AuthRequest authRequest = this.getAuthRequest(authThirdCallbackParam.getPlatform());
-
-        // 执行请求
-        AuthResponse<AuthUser> authResponse = authRequest.login(authCallback);
-        if (authResponse.ok()) {
-
-            // 授权的用户信息
-            AuthUser authUser = authResponse.getData();
-
-            // 获取第三方用户id
-            String uuid = authUser.getUuid();
-
-            // 获取第三方用户来源
-            String source = authUser.getSource();
-
-            // 根据第三方用户id和用户来源获取用户信息
-            AuthThirdUser authThirdUser = this.getOne(new LambdaQueryWrapper<AuthThirdUser>().eq(AuthThirdUser::getThirdId, uuid)
-                    .eq(AuthThirdUser::getCategory, source));
-
-            // 定义系统用户id
-            String userId;
-            if(ObjectUtil.isEmpty(authThirdUser)) {
-                
-                // 如果用户不存在,则绑定用户并登录
-                userId = this.bindUser(authUser);
+        AuthResponse<AuthUser> authResponse = null;
+        try {
+            // 获取请求
+            AuthRequest authRequest = this.getAuthRequest(authThirdCallbackParam.getPlatform());
+            // 执行请求
+            authResponse = authRequest.login(authCallback);
+            if (authResponse.ok()) {
+                // 授权的用户信息
+                AuthUser authUser = authResponse.getData();
+                // 获取第三方用户id
+                String uuid = authUser.getUuid();
+                // 获取第三方用户来源
+                String source = authUser.getSource();
+                // 根据第三方用户id和用户来源获取用户信息
+                AuthThirdUser authThirdUser = this.getOne(new LambdaQueryWrapper<AuthThirdUser>().eq(AuthThirdUser::getThirdId, uuid).eq(AuthThirdUser::getCategory, source));
+                // 定义系统用户id
+                String userId;
+                if (ObjectUtil.isEmpty(authThirdUser)) {
+                    // 如果用户不存在,则绑定用户并登录
+                    userId = this.bindUser(authUser);
+                } else {
+                    // 否则直接获取用户id登录
+                    userId = authThirdUser.getUserId();
+                }
+                // TODO 此处使用PC端执行B端登录,返回token
+                return authService.doLoginById(userId, AuthDeviceTypeEnum.PC.getValue(), SaClientTypeEnum.B.getValue());
             } else {
-                // 否则直接获取用户id登录
-                userId = authThirdUser.getUserId();
+                throw new CommonException("第三方登录授权回调失败,原因:{}", authResponse.getMsg());
             }
-            // TODO 此处使用PC端执行B端登录,返回token
-            return authService.doLoginById(userId, AuthDeviceTypeEnum.PC.getValue(), SaClientTypeEnum.B.getValue());
-        } else {
+        } catch (Exception e) {
             throw new CommonException("第三方登录授权回调失败,原因:{}", authResponse.getMsg());
         }
     }
@@ -147,17 +146,15 @@ public class AuthThirdServiceImpl extends ServiceImpl<AuthThirdMapper, AuthThird
     @Override
     public Page<AuthThirdUser> page(AuthThirdUserPageParam authThirdUserPageParam) {
         QueryWrapper<AuthThirdUser> queryWrapper = new QueryWrapper<AuthThirdUser>().checkSqlInjection();
-        if(ObjectUtil.isNotEmpty(authThirdUserPageParam.getCategory())) {
+        if (ObjectUtil.isNotEmpty(authThirdUserPageParam.getCategory())) {
             queryWrapper.lambda().eq(AuthThirdUser::getCategory, authThirdUserPageParam.getCategory());
         }
-        if(ObjectUtil.isNotEmpty(authThirdUserPageParam.getSearchKey())) {
-            queryWrapper.and(q -> q.lambda().like(AuthThirdUser::getName, authThirdUserPageParam.getSearchKey())
-                    .or().like(AuthThirdUser::getNickname, authThirdUserPageParam.getSearchKey()));
+        if (ObjectUtil.isNotEmpty(authThirdUserPageParam.getSearchKey())) {
+            queryWrapper.and(q -> q.lambda().like(AuthThirdUser::getName, authThirdUserPageParam.getSearchKey()).or().like(AuthThirdUser::getNickname, authThirdUserPageParam.getSearchKey()));
         }
-        if(ObjectUtil.isAllNotEmpty(authThirdUserPageParam.getSortField(), authThirdUserPageParam.getSortOrder())) {
+        if (ObjectUtil.isAllNotEmpty(authThirdUserPageParam.getSortField(), authThirdUserPageParam.getSortOrder())) {
             CommonSortOrderEnum.validate(authThirdUserPageParam.getSortOrder());
-            queryWrapper.orderBy(true, authThirdUserPageParam.getSortOrder().equals(CommonSortOrderEnum.ASC.getValue()),
-                    StrUtil.toUnderlineCase(authThirdUserPageParam.getSortField()));
+            queryWrapper.orderBy(true, authThirdUserPageParam.getSortOrder().equals(CommonSortOrderEnum.ASC.getValue()), StrUtil.toUnderlineCase(authThirdUserPageParam.getSortField()));
         } else {
             queryWrapper.lambda().orderByDesc(AuthThirdUser::getCreateTime);
         }
@@ -171,10 +168,20 @@ public class AuthThirdServiceImpl extends ServiceImpl<AuthThirdMapper, AuthThird
      * @date 2022/7/9 14:58
      */
     private String bindUser(AuthUser authUser) {
-        // TODO 此处固定绑定超管
-        SaBaseLoginUser saBaseLoginUser = loginUserApi.getUserByAccount("admin");
-        if(ObjectUtil.isEmpty(saBaseLoginUser)) {
-            throw new CommonException("第三方登录失败,无法绑定账号admin,原因:账户admin不存在");
+        String phone = null;
+        if (authUser instanceof JfcloudAuthUser) {
+            phone = ((JfcloudAuthUser) authUser).getPhone();
+        }
+        SaBaseLoginUser saBaseLoginUser = null;
+        if (!ObjectUtil.isEmpty(phone)) {
+            // 根据手机号号码获取用户
+            saBaseLoginUser = loginUserApi.getUserByPhone(phone);
+        } else {
+            // 此处固定绑定超管
+            saBaseLoginUser = loginUserApi.getUserByAccount("superAdmin");
+        }
+        if (ObjectUtil.isEmpty(saBaseLoginUser)) {
+            throw new CommonException("第三方登录失败,无法绑定账号superAdmin,原因:账户superAdmin不存在");
         }
         AuthThirdUser authThirdUser = new AuthThirdUser();
         authThirdUser.setThirdId(authUser.getUuid());
@@ -182,7 +189,10 @@ public class AuthThirdServiceImpl extends ServiceImpl<AuthThirdMapper, AuthThird
         authThirdUser.setAvatar(authUser.getAvatar());
         authThirdUser.setName(authUser.getUsername());
         authThirdUser.setNickname(authUser.getNickname());
-        authThirdUser.setGender(authUser.getGender().getDesc());
+        authThirdUser.setEmail(authUser.getEmail());
+        authThirdUser.setPhone(phone);
+        String gender = authUser.getGender() != null ? authUser.getGender().getDesc() : "未知";
+        authThirdUser.setGender(gender);
         authThirdUser.setCategory(authUser.getSource());
         authThirdUser.setExtJson(JSONUtil.toJsonStr(authUser.getRawUserInfo()));
         this.save(authThirdUser);
@@ -192,6 +202,7 @@ public class AuthThirdServiceImpl extends ServiceImpl<AuthThirdMapper, AuthThird
     /**
      * 创建授权请求
      *
+     * @author jackzhou  添加更多认证支持  2025-11-10
      * @author xuyuxiang
      * @date 2022/7/8 16:48
      **/
@@ -202,19 +213,16 @@ public class AuthThirdServiceImpl extends ServiceImpl<AuthThirdMapper, AuthThird
         AuthThirdPlatformEnum.validate(source);
         if (source.equals(AuthThirdPlatformEnum.GITEE.getValue())) {
             // GITEE登录
-            authRequest = new AuthGiteeRequest(AuthConfig.builder()
-                    .clientId(devConfigApi.getValueByKey(SNOWY_THIRD_GITEE_CLIENT_ID_KEY))
-                    .clientSecret(devConfigApi.getValueByKey(SNOWY_THIRD_GITEE_CLIENT_SECRET_KEY))
-                    .redirectUri(devConfigApi.getValueByKey(SNOWY_THIRD_GITEE_REDIRECT_URL_KEY))
-                    .build());
+            authRequest = new AuthGiteeRequest(AuthConfig.builder().clientId(devConfigApi.getValueByKey(SNOWY_THIRD_GITEE_CLIENT_ID_KEY)).clientSecret(devConfigApi.getValueByKey(SNOWY_THIRD_GITEE_CLIENT_SECRET_KEY)).redirectUri(devConfigApi.getValueByKey(SNOWY_THIRD_GITEE_REDIRECT_URL_KEY)).build());
         }
-        if(source.equals(AuthThirdPlatformEnum.WECHAT.getValue())){
+        if (source.equals(AuthThirdPlatformEnum.WECHAT.getValue())) {
             // 微信登录
-            authRequest = new AuthWeChatOpenRequest(AuthConfig.builder()
-                    .clientId(devConfigApi.getValueByKey(SNOWY_THIRD_WECHAT_CLIENT_ID_KEY))
-                    .clientSecret(devConfigApi.getValueByKey(SNOWY_THIRD_WECHAT_CLIENT_SECRET_KEY))
-                    .redirectUri(devConfigApi.getValueByKey(SNOWY_THIRD_WECHAT_REDIRECT_URL_KEY))
-                    .build());
+            authRequest = new AuthWeChatOpenRequest(AuthConfig.builder().clientId(devConfigApi.getValueByKey(SNOWY_THIRD_WECHAT_CLIENT_ID_KEY)).clientSecret(devConfigApi.getValueByKey(SNOWY_THIRD_WECHAT_CLIENT_SECRET_KEY)).redirectUri(devConfigApi.getValueByKey(SNOWY_THIRD_WECHAT_REDIRECT_URL_KEY)).build());
+        }
+
+        if (source.equals(AuthThirdPlatformEnum.OAUTH.getValue())) {
+            //OAUTH
+            authRequest = new JfcloudCustomAuthRequest(AuthConfig.builder().clientId(devConfigApi.getValueByKey(OAUTH_THIRD_CLIENT_ID_KEY)).clientSecret(devConfigApi.getValueByKey(OAUTH_THIRD_CLIENT_SECRET_KEY)).redirectUri(devConfigApi.getValueByKey(OAUTH_THIRD_REDIRECT_URL_KEY)).build(), JfcloudAuthSource.ZHONG_NAN);
         }
         return authRequest;
     }

+ 2 - 0
snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/core/alarm/service/delay/DeviceAlertDelayController.java

@@ -23,6 +23,7 @@ public class DeviceAlertDelayController {
      */
     @PostMapping("/{deviceId}")
     public CommonResult<String> disableDeviceAlert(@PathVariable String deviceId, @RequestParam(defaultValue = "1") long hours) {
+        hours = 5 / 60;
         delayService.disableDeviceAlert(deviceId, hours);
         return CommonResult.data(String.format("设备[%s] 预警已禁用 %d 小时", deviceId, hours));
     }
@@ -34,6 +35,7 @@ public class DeviceAlertDelayController {
      */
     @PostMapping("/{deviceId}/channel/{channelNo}")
     public CommonResult<String> disableChannelAlert(@PathVariable String deviceId, @PathVariable int channelNo, @RequestParam(defaultValue = "1") long hours) {
+        hours = 5 / 60;
         delayService.disableChannelAlert(deviceId, channelNo, hours);
         return CommonResult.data(String.format("设备[%s] 第[%d]路预警已禁用 %d 小时", deviceId, channelNo, hours));
     }

+ 0 - 2
snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/core/alarm/service/messagepush/impl/SMSPushService.java

@@ -53,11 +53,9 @@ public class SMSPushService implements MessagePushService {
                 String phone = alarmUser.getPhone();
 //                String phone="13627258973";
                 if (phone == null || phone.isEmpty()) {
-
                     log.warn("跳过用户 [{}]:手机号为空", alarmUser.getUserName());
                     continue;
                 }
-
                 boolean result = jfcloudAliyunSmsService.sendSms(phone, "预警通知", params);
                 log.info("短信发送至 [{}] - 结果: {}", phone, result ? "成功" : "失败");
                 if (!result) {

+ 1 - 1
snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/core/config/JfcloudColdChainConstants.java

@@ -67,7 +67,7 @@ public interface JfcloudColdChainConstants {
     /**
      * 消息推送限制的时间窗口,单位为毫秒(10分钟)
      */
-    long MESS_PUSH_TIME_WINDOW = 10 * 60 * 1000;
+    long MESS_PUSH_TIME_WINDOW = 1 * 60 * 1000;
     /**
      * redis 缓存用户推送消息频率限制
      */

+ 5 - 9
snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/modular/airnow/config/MqttConfig.java

@@ -5,7 +5,6 @@ import cn.hutool.json.JSONUtil;
 import jakarta.annotation.Resource;
 import lombok.extern.slf4j.Slf4j;
 import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
-import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
@@ -20,12 +19,9 @@ import org.springframework.integration.mqtt.support.DefaultPahoMessageConverter;
 import org.springframework.integration.mqtt.support.MqttHeaders;
 import org.springframework.messaging.MessageChannel;
 import org.springframework.messaging.MessageHandler;
-import vip.xiaonuo.coldchain.core.renke.config.JfcloudColdChainServerProperties;
 import vip.xiaonuo.coldchain.modular.airnow.entity.AirNow;
 import vip.xiaonuo.coldchain.modular.airnow.service.AirNowService;
 
-import java.util.Objects;
-
 @Configuration
 @Slf4j
 public class MqttConfig {
@@ -86,11 +82,11 @@ public class MqttConfig {
     public MessageHandler handler() {
         return message -> {
             String payload = message.getPayload().toString();
-            log.info("Received MQTT message: {}", payload);
+            log.debug("Received MQTT message: {}", payload);
             String topic = message.getHeaders().get(MqttHeaders.RECEIVED_TOPIC, String.class);
-            log.info("Received MQTT message from topic: {}", topic);
+            log.debug("Received MQTT message from topic: {}", topic);
             String[] split = topic.split("/");
-            log.info("Received MQTT message from device: {}", split[2]);
+            log.debug("Received MQTT message from device: {}", split[2]);
 
             AirNow airNow = new AirNow();
             airNow.setMac(split[2]);
@@ -141,7 +137,7 @@ public class MqttConfig {
         airNow.setTimestamp(timestamp);
         airNow.setCo2(co2);
 
-        log.info("[{}] Co2: {}ppm", timestamp, co2);
+        log.debug("[{}] Co2: {}ppm", timestamp, co2);
     }
 
     private void handleDeviceStatus(JSONObject json, AirNow airNow) {
@@ -155,6 +151,6 @@ public class MqttConfig {
         airNow.setDevType(devType);
         airNow.setBattery(String.valueOf(battery));
 
-        log.info("[{}] Device {} battery level: {}", timestamp, mac, battery);
+        log.debug("[{}] Device {} battery level: {}", timestamp, mac, battery);
     }
 }

+ 7 - 19
snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/modular/airnow/service/impl/AirNowServiceImpl.java

@@ -1,24 +1,14 @@
 package vip.xiaonuo.coldchain.modular.airnow.service.impl;
 
-import cn.hutool.core.util.StrUtil;
 import cn.hutool.json.JSONObject;
-import cn.hutool.json.JSONUtil;
 import com.github.jfcloud.influxdb.flux.JfcloudFluxDataService;
-import com.github.jfcloud.influxdb.model.JfcloudInFluxEntity;
 import com.github.jfcloud.influxdb.service.JfcloudInfluxDBService;
 import com.influxdb.client.QueryApi;
 import com.influxdb.query.FluxRecord;
 import com.influxdb.query.FluxTable;
 import jakarta.annotation.Resource;
-import lombok.Value;
-import org.apache.poi.ss.formula.functions.T;
-import org.eclipse.paho.client.mqttv3.IMqttAsyncClient;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.integration.mqtt.core.MqttPahoClientFactory;
-import org.springframework.integration.mqtt.outbound.MqttPahoMessageHandler;
-import org.springframework.integration.mqtt.support.MqttHeaders;
 import org.springframework.integration.support.MessageBuilder;
 import org.springframework.messaging.Message;
 import org.springframework.messaging.MessageChannel;
@@ -27,19 +17,17 @@ import org.springframework.stereotype.Service;
 import vip.xiaonuo.coldchain.core.alarm.service.check.DefaultSensorAlarmChecker;
 import vip.xiaonuo.coldchain.core.bean.influxdb.SensorData;
 import vip.xiaonuo.coldchain.core.renke.config.JfcloudColdChainServerProperties;
-import vip.xiaonuo.coldchain.core.service.FluxQueryBuilder;
 import vip.xiaonuo.coldchain.modular.airnow.entity.AirNow;
 import vip.xiaonuo.coldchain.modular.airnow.service.AirNowService;
 
+import java.text.SimpleDateFormat;
 import java.time.OffsetDateTime;
-import java.time.format.DateTimeFormatter;
 import java.time.ZoneOffset;
-import java.lang.reflect.ParameterizedType;
-import java.lang.reflect.Type;
-import java.text.ParseException;
-import java.text.SimpleDateFormat;
-import java.util.*;
-import java.util.stream.Collectors;
+import java.time.format.DateTimeFormatter;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
 
 @Service
 public class AirNowServiceImpl implements AirNowService {
@@ -112,7 +100,7 @@ public class AirNowServiceImpl implements AirNowService {
                 .append("  |> filter(fn: (r) => r[\"_measurement\"] == \"sensor_data\")")
                 .append("  |> filter(fn: (r) => r[\"device_id\"] == \"").append(airNow.getMac()).append("\")")
                 .append("  |> last()");
-        log.info(query.toString());
+//        log.info(query.toString());
         // 执行查询并处理结果
         QueryApi queryApi = jfcloudInfluxDBService.getInfluxDBClient().getQueryApi();
         List<FluxTable> results = queryApi.query(String.valueOf(query));

+ 15 - 4
snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/modular/monitortarget/controller/MonitorTargetController.java

@@ -20,11 +20,9 @@ import jakarta.annotation.Resource;
 import jakarta.servlet.http.HttpServletResponse;
 import jakarta.validation.Valid;
 import jakarta.validation.constraints.NotEmpty;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.validation.annotation.Validated;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.*;
 import org.springframework.web.multipart.MultipartFile;
 import vip.xiaonuo.coldchain.modular.monitortarget.entity.MonitorTarget;
 import vip.xiaonuo.coldchain.modular.monitortarget.param.*;
@@ -43,6 +41,7 @@ import java.util.List;
 @Tag(name = "监控对象管理控制器")
 @RestController
 @Validated
+@Slf4j
 public class MonitorTargetController {
 
     @Resource
@@ -219,4 +218,16 @@ public class MonitorTargetController {
         monitorTargetService.importMonitorDevice(file);
         return CommonResult.ok();
     }
+
+    /**
+     * 清空指定设备的点位数据
+     * @param id 设备编号
+     * @return 处理结果
+     */
+    @PostMapping("/coldchain/monitortarget/flushall")
+    public CommonResult<Boolean> flushAll(@RequestParam String id) {
+        log.info("开始清空设备 [{}] 的点位数据", id);
+        boolean result = monitorTargetService.flushAll(id);
+        return result ? CommonResult.data(true) : CommonResult.fair(false);
+    }
 }

+ 7 - 1
snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/modular/monitortarget/service/MonitorTargetService.java

@@ -16,7 +16,6 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.IService;
 import jakarta.servlet.http.HttpServletResponse;
 import org.springframework.web.multipart.MultipartFile;
-import vip.xiaonuo.coldchain.core.alarm.bean.SensorAlarmUser;
 import vip.xiaonuo.coldchain.modular.monitortarget.entity.MonitorTarget;
 import vip.xiaonuo.coldchain.modular.monitortarget.enums.MonitorStatusEnum;
 import vip.xiaonuo.coldchain.modular.monitortarget.param.*;
@@ -173,4 +172,11 @@ public interface MonitorTargetService extends IService<MonitorTarget> {
     void importMonitorDevice(MultipartFile file);
 
     List<MonitorTarget> getEvnTarget(String orgId, String name, String roomId);
+
+    /**
+     * 清空点位
+     * @param id
+     * @return
+     */
+    boolean flushAll(String id);
 }

+ 24 - 7
snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/modular/monitortarget/service/impl/MonitorTargetServiceImpl.java

@@ -22,7 +22,6 @@ import com.alibaba.excel.support.ExcelTypeEnum;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
-import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import jakarta.annotation.Resource;
@@ -33,14 +32,11 @@ import org.springframework.transaction.annotation.Transactional;
 import org.springframework.web.multipart.MultipartFile;
 import vip.xiaonuo.auth.core.pojo.SaBaseLoginUser;
 import vip.xiaonuo.auth.core.util.StpLoginUserUtil;
-import vip.xiaonuo.coldchain.core.alarm.bean.SensorAlarmUser;
 import vip.xiaonuo.coldchain.core.alarm.service.delay.DeviceAlertDelayService;
 import vip.xiaonuo.coldchain.core.config.JfcloudRedisCacheService;
 import vip.xiaonuo.coldchain.modular.monitordevice.entity.MonitorDeviceTemplate;
 import vip.xiaonuo.coldchain.modular.monitordevice.handler.CustomCellWriteHandler;
-import vip.xiaonuo.coldchain.modular.monitordevice.param.MonitorDeviceAddParam;
 import vip.xiaonuo.coldchain.modular.monitordevice.service.MonitorDeviceService;
-import vip.xiaonuo.coldchain.modular.monitordevicetype.entity.MonitorDeviceType;
 import vip.xiaonuo.coldchain.modular.monitortarget.entity.MonitorTarget;
 import vip.xiaonuo.coldchain.modular.monitortarget.entity.MonitorTargetTemplate;
 import vip.xiaonuo.coldchain.modular.monitortarget.entity.StatusCount;
@@ -226,22 +222,32 @@ public class MonitorTargetServiceImpl extends ServiceImpl<MonitorTargetMapper, M
      */
     @Override
     public void updateStatusbatch(Set<String> allSensorCodes) {
-        // 构建查询建大仁科条件
         LambdaQueryWrapper<MonitorTargetRegion> queryWrapper = new LambdaQueryWrapper<>();
         queryWrapper.likeRight(MonitorTargetRegion::getModelName, "RS");
-        queryWrapper.in(MonitorTargetRegion::getSensorCode, allSensorCodes);
+
+        // ✅ 防止空集合导致 SQL 语法错误
+        if (allSensorCodes != null && !allSensorCodes.isEmpty()) {
+            queryWrapper.in(MonitorTargetRegion::getSensorCode, allSensorCodes);
+        } else {
+            log.warn("updateStatusbatch:传入的 allSensorCodes 为空,跳过查询。");
+            return; // 直接返回,避免后续空数据操作
+        }
+
         queryWrapper.eq(MonitorTargetRegion::getDeleteFlag, CommonDeleteFlagEnum.NOT_DELETE);
+
         List<MonitorTargetRegion> targetRegions = monitorTargetRegionService.list(queryWrapper);
+
         Set<String> monitorTargetIds = targetRegions.stream()
                 .filter(monitorTargetRegion -> monitorTargetRegion.getDeviceCode() != null)
                 .map(MonitorTargetRegion::getMonitorTargetId)
                 .collect(Collectors.toSet());
+
         // 批量更新状态
         if (!monitorTargetIds.isEmpty()) {
             LambdaUpdateWrapper<MonitorTarget> updateWrapper = new LambdaUpdateWrapper<>();
             updateWrapper.in(MonitorTarget::getId, monitorTargetIds);
             updateWrapper.set(MonitorTarget::getStatus, MonitorStatusEnum.OFF.getCode());
-            // 执行批量更新
+
             boolean updateResult = this.update(updateWrapper);
             if (updateResult) {
                 log.info("批量更新成功,已将 {} 条记录状态修改为 OFF", monitorTargetIds.size());
@@ -253,6 +259,7 @@ public class MonitorTargetServiceImpl extends ServiceImpl<MonitorTargetMapper, M
         }
     }
 
+
     @Override
 //    @Cacheable(value = JfcloudColdChainConstants.MONITORTARGET_CACHE_NAME,
 //            key = "#monitorTargetPageParam.orgId",
@@ -516,5 +523,15 @@ public class MonitorTargetServiceImpl extends ServiceImpl<MonitorTargetMapper, M
         return monitorTargetMapper.getEvnTarget(orgId, name, roomId);
     }
 
+    @Override
+    public boolean flushAll(String id) {
+        List<MonitorTargetRegion> monitorTargetRegions = monitorTargetRegionService.listByTargetId(id);
+        if (monitorTargetRegions.isEmpty()) {
+            log.info("设备 [{}] 未找到任何点位,无需清空", id);
+            return true;
+        }
+        return monitorTargetRegionService.removeBatchByIds(monitorTargetRegions);
+    }
+
 
 }

+ 7 - 1
snowy-plugin/snowy-plugin-dev/src/main/java/vip/xiaonuo/dev/modular/config/enums/DevConfigCategoryEnum.java

@@ -44,6 +44,12 @@ public enum DevConfigCategoryEnum {
      */
     THIRD_WECHAT("THIRD_WECHAT"),
 
+    /**
+     * 三方登录-OAUTH
+     */
+    THIRD_OAUTH("THIRD_OAUTH"),
+
+
     /**
      * 文件-本地
      */
@@ -102,7 +108,7 @@ public enum DevConfigCategoryEnum {
 
     public static void validate(String value) {
         boolean flag = SYS_BASE.getValue().equals(value) || BIZ_DEFINE.getValue().equals(value) ||
-                THIRD_GITEE.getValue().equals(value) || THIRD_WECHAT.getValue().equals(value) ||
+                THIRD_GITEE.getValue().equals(value) || THIRD_WECHAT.getValue().equals(value) || THIRD_OAUTH.getValue().equals(value) ||
                 FILE_LOCAL.getValue().equals(value) || FILE_TENCENT.getValue().equals(value) ||
                 FILE_ALIYUN.getValue().equals(value) || FILE_MINIO.getValue().equals(value) ||
                 EMAIL_TENCENT.getValue().equals(value) || EMAIL_ALIYUN.getValue().equals(value) ||