فهرست منبع

新增小程序登录接口

tsurumure 5 ماه پیش
والد
کامیت
174a3e8167

+ 1 - 1
db/app_user.sql

@@ -12,7 +12,7 @@ CREATE TABLE `app_user` (
    `nickname` VARCHAR(20) COMMENT '昵称',
    `phone` VARCHAR(20) COMMENT '手机号码',
    `email` VARCHAR(50) COMMENT '邮箱地址',
-   `password` VARCHAR(100) NOT NULL COMMENT '密码',
+   `password` VARCHAR(100) COMMENT '密码',
    `gender` TINYINT(1) DEFAULT NULL COMMENT '性别(1男, 2女, 3保密)',
    `avatar` VARCHAR(1000) DEFAULT NULL COMMENT '头像',
    `last_login_uuid` VARCHAR(36) COMMENT '最后登录UUID',

+ 11 - 3
src/main/java/com/backendsys/modules/app/controller/AppAuthController.java

@@ -1,11 +1,11 @@
 package com.backendsys.modules.app.controller;
 
 import com.backendsys.modules.app.entity.AppAuth;
-import com.backendsys.modules.app.entity.AppUser;
 import com.backendsys.modules.app.service.AppAuthService;
-import com.backendsys.modules.app.service.AppUserService;
 import com.backendsys.modules.common.config.security.annotations.Anonymous;
 import com.backendsys.modules.common.utils.Result;
+import com.backendsys.modules.sdk.wechat.miniprogram.entity.WechatAuth;
+import com.backendsys.modules.sdk.wechat.miniprogram.service.WechatAuthService;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -22,6 +22,9 @@ public class AppAuthController {
     @Autowired
     private AppAuthService appAuthService;
 
+    @Autowired
+    private WechatAuthService wechatAuthService;
+
     @Anonymous
     @Operation(summary = "APP登录 (用户名登录)")
     @PostMapping("/api/app/auth/login")
@@ -29,6 +32,11 @@ public class AppAuthController {
         return Result.success().put("data", appAuthService.login(appAuth));
     }
 
-
+    @Anonymous
+    @Operation(summary = "微信小程序用户登录")
+    @PostMapping("/api/app/auth/loginWithWechatMiniprogram")
+    public Result loginWithWechatMiniprogram(@Validated(WechatAuth.Login.class) @RequestBody WechatAuth wechatAuth) {
+        return Result.success().put("data", wechatAuthService.loginWithWechatMiniprogram(wechatAuth));
+    }
 
 }

+ 2 - 0
src/main/java/com/backendsys/modules/app/entity/AppUser.java

@@ -37,6 +37,8 @@ public class AppUser {
 
     @TableField(exist = false)
     private String role;
+    @TableField(exist = false)
+    private String session_key;
 
     @TableField(exist = false)
     private String token_expiration;

+ 0 - 2
src/main/java/com/backendsys/modules/app/service/AppAuthService.java

@@ -3,8 +3,6 @@ package com.backendsys.modules.app.service;
 import com.backendsys.modules.app.entity.AppAuth;
 import com.backendsys.modules.app.entity.AppUser;
 
-import java.util.Map;
-
 public interface AppAuthService {
 
     // APP登录 (用户名登录)

+ 0 - 4
src/main/java/com/backendsys/modules/app/service/impl/AppAuthServiceImpl.java

@@ -11,7 +11,6 @@ import com.backendsys.modules.app.entity.AppUser;
 import com.backendsys.modules.app.service.AppAuthService;
 import com.backendsys.modules.common.config.redis.utils.RedisUtil;
 import com.backendsys.modules.common.config.security.entity.SecurityAppUserInfo;
-import com.backendsys.modules.common.config.security.entity.SecurityUserInfo;
 import com.backendsys.modules.common.config.security.utils.*;
 import com.backendsys.modules.system.entity.TokenCatch;
 import com.backendsys.modules.system.service.SysCommonService;
@@ -23,8 +22,6 @@ import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
 import org.springframework.stereotype.Service;
 
 import java.util.Date;
-import java.util.List;
-import java.util.Map;
 import java.util.UUID;
 import java.util.concurrent.TimeUnit;
 
@@ -161,5 +158,4 @@ public class AppAuthServiceImpl implements AppAuthService {
 
     }
 
-
 }

+ 0 - 12
src/main/java/com/backendsys/modules/common/aspect/AppUserLoginAspect.java

@@ -28,22 +28,10 @@ public class AppUserLoginAspect {
 
     @Before("checkLogin()")
     public void doCheckLogin(JoinPoint joinPoint) throws Throwable {
-        // 获取请求信息
-        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
-
-//        // 获取Token
-//        String token = request.getHeader("Authorization");
-//        System.out.println("(AppUserLoginAspect) token = " + token);
-//        jwtUtil.extractAllClaims(token);
         SecurityAppUserInfo userInfo = SecurityUtil.getAppUserInfo();
         String tokenRole = Convert.toStr(userInfo.getRole());
         if (!"APP_USER".equals(tokenRole)) {
             throw new CustException("当前接口与 Token 的类型不匹配");
         }
-        System.out.println(userInfo);
-
-//        if (token == null) throw new CustException("未登录或Token无效");
-
-
     }
 }

+ 8 - 6
src/main/java/com/backendsys/modules/common/config/security/utils/SecurityUtil.java

@@ -102,7 +102,7 @@ public class SecurityUtil {
      */
     public static Boolean isSuper() {
         SecurityUserInfo userInfo = getUserInfo();
-        return userInfo.getIs_super() == 1;
+        return userInfo.getIs_super() != null && userInfo.getIs_super() == 1;
     }
 
 //    public static Boolean hasPermission(String permission) {
@@ -130,7 +130,7 @@ public class SecurityUtil {
     public Boolean hasPermission(String permission) {
         if (isSuper()) return true;
         List<String> permission_ids = getPermissionIds();
-        return permission_ids.contains(permission);
+        return permission_ids != null && permission_ids.contains(permission);
     }
 
     /**
@@ -142,10 +142,12 @@ public class SecurityUtil {
     public Boolean hasPermissions(List<String> permis, MatchType matchType) {
         if (isSuper()) return true;
         List<String> permission_ids = getPermissionIds();
-        if (matchType.equals(MatchType.AND)) {
-            return permis.stream().allMatch(permission_ids::contains);
-        } else if (matchType.equals(MatchType.OR)) {
-            return permis.stream().anyMatch(permission_ids::contains);
+        if (permission_ids != null) {
+            if (matchType.equals(MatchType.AND)) {
+                return permis.stream().allMatch(permission_ids::contains);
+            } else if (matchType.equals(MatchType.OR)) {
+                return permis.stream().anyMatch(permission_ids::contains);
+            }
         }
         return false;
     }

+ 14 - 0
src/main/java/com/backendsys/modules/sdk/wechat/miniprogram/entity/WechatAuth.java

@@ -0,0 +1,14 @@
+package com.backendsys.modules.sdk.wechat.miniprogram.entity;
+
+import jakarta.validation.constraints.NotEmpty;
+import lombok.Data;
+
+@Data
+public class WechatAuth {
+
+    public static interface Login{}
+
+    @NotEmpty(message = "code 不能为空", groups = { Login.class })
+    private String code;
+
+}

+ 11 - 0
src/main/java/com/backendsys/modules/sdk/wechat/miniprogram/service/WechatAuthService.java

@@ -0,0 +1,11 @@
+package com.backendsys.modules.sdk.wechat.miniprogram.service;
+
+import com.backendsys.modules.app.entity.AppUser;
+import com.backendsys.modules.sdk.wechat.miniprogram.entity.WechatAuth;
+
+public interface WechatAuthService {
+
+    // 微信小程序用户登录
+    AppUser loginWithWechatMiniprogram(WechatAuth wechatAuth);
+
+}

+ 143 - 0
src/main/java/com/backendsys/modules/sdk/wechat/miniprogram/service/impl/WechatAuthServiceImpl.java

@@ -0,0 +1,143 @@
+package com.backendsys.modules.sdk.wechat.miniprogram.service.impl;
+
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.date.DateUnit;
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.util.RandomUtil;
+import cn.hutool.json.JSONObject;
+import cn.hutool.json.JSONUtil;
+import com.backendsys.exception.CustException;
+import com.backendsys.modules.app.dao.AppUserDao;
+import com.backendsys.modules.app.entity.AppUser;
+import com.backendsys.modules.common.config.redis.utils.RedisUtil;
+import com.backendsys.modules.common.config.security.entity.SecurityAppUserInfo;
+import com.backendsys.modules.common.config.security.utils.HttpRequestUtil;
+import com.backendsys.modules.common.config.security.utils.JwtUtil;
+import com.backendsys.modules.common.config.security.utils.TokenUtil;
+import com.backendsys.modules.sdk.wechat.miniprogram.entity.WechatAuth;
+import com.backendsys.modules.sdk.wechat.miniprogram.service.WechatAuthService;
+import com.backendsys.modules.sdk.wechat.miniprogram.utils.WechatUtil;
+import com.backendsys.modules.system.entity.TokenCatch;
+import com.backendsys.modules.system.service.SysCommonService;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+import java.util.Date;
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+
+@Service
+public class WechatAuthServiceImpl implements WechatAuthService {
+
+    @Autowired
+    private JwtUtil jwtUtil;
+    @Autowired
+    private TokenUtil tokenUtil;
+    @Autowired
+    private RedisUtil redisUtil;
+    @Autowired
+    private WechatUtil wechatUtil;
+    @Autowired
+    private HttpRequestUtil httpRequestUtil;
+
+    @Value("${REDIS_LOGIN_TOKEN_PREFIX}")
+    private String REDIS_LOGIN_TOKEN_PREFIX;
+
+    @Autowired
+    private SysCommonService sysCommonService;
+    @Autowired
+    private AppUserDao appUserDao;
+
+
+    // [方法] 登录成功
+    private AppUser loginSuccess(AppUser appUser) {
+
+        // 删除旧的登录缓存
+        tokenUtil.deleteRedisLoginToken(appUser.getLast_login_uuid());
+
+        // 判断用户是否启用
+        Integer status = appUser.getStatus();
+        if (status != null && status.equals(-1)) throw new CustException("该用户已被禁用,请与客服联系");
+
+        // 判断用户是否已删除
+        Integer del_flag = appUser.getDel_flag();
+        if (del_flag != null && del_flag.equals(1)) throw new CustException("当前用户不可用,请与客服联系");
+
+        // [系统配置] 微信用户默认登录过期时间(小时)
+        Integer APP_USER_LOGIN_DURATION_DEFAULT = Convert.toInt(sysCommonService.getCommonByTag("APP_USER_LOGIN_DURATION_DEFAULT"));
+        // 将小时转换为毫秒
+        Long DEFAULT_MILLISECONDS = APP_USER_LOGIN_DURATION_DEFAULT * DateUnit.HOUR.getMillis();
+        Integer token_duration_hours = Convert.toInt(DEFAULT_MILLISECONDS / 3600000L);
+
+        Date token_expiration = new Date((new Date()).getTime() + DEFAULT_MILLISECONDS);
+        appUser.setToken_expiration(DateUtil.format(token_expiration, "yyyy-MM-dd HH:mm:ss"));
+        appUser.setRole("APP_USER");
+
+        // 生成 Token
+        SecurityAppUserInfo securityUserInfo = JSONUtil.toBean(JSONUtil.parseObj(appUser), SecurityAppUserInfo.class);
+
+        String token = jwtUtil.createAppJwtToken(securityUserInfo);
+        String token_redis_key = REDIS_LOGIN_TOKEN_PREFIX + appUser.getLast_login_uuid();
+        appUser.setToken(token);
+
+        // [Redis] 将 Token 存入缓存
+        TokenCatch tokenCatch = new TokenCatch(token, null);
+        redisUtil.setCacheObject(token_redis_key, JSONUtil.toJsonStr(tokenCatch), token_duration_hours, TimeUnit.HOURS);
+
+        return appUser;
+    }
+
+    /**
+     * 微信小程序用户登录
+     */
+    @Override
+    public AppUser loginWithWechatMiniprogram(WechatAuth wechatAuth) {
+
+        String code = wechatAuth.getCode();
+        JSONObject response = wechatUtil.getCode2Session(code);
+        // [失败] 返回值:{"errcode":40164,"errmsg":"invalid ip 116.31.165.86 ipv6 ::ffff:116.31.165.86, not in whitelist, rid: 67d15951-5b3f2778-79d6f047"}
+        // [成功] 返回值:{"session_key":"uQAcry2PC1Lx/Krp+6rr0g==","openid":"oSB9r7T7kN1bC7PabJ7RmTUiaJmo"}
+
+        // - errcode: 错误码,请求失败时返回
+        if (!response.containsKey("errcode")) {
+
+            String openid = response.getStr("openid");
+            String session_key = response.getStr("session_key");
+
+            String uuid = Convert.toStr(UUID.randomUUID());
+            String last_login_ip = httpRequestUtil.getIpAddr();
+            String last_login_time = DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss");
+
+            // [Get] 判断用户是否存在 (openid)
+            AppUser appUser = appUserDao.selectOne(new LambdaQueryWrapper<AppUser>().eq(AppUser::getWechat_open_id, openid));
+            if (appUser == null) {
+                // [Insert] 不存在,则创建
+                appUser = new AppUser();
+                appUser.setWechat_open_id(openid);
+                appUser.setNickname("微信用户" + RandomUtil.randomStringUpper(4));
+                //
+                appUser.setLast_login_uuid(uuid);
+                appUser.setLast_login_ip(last_login_ip);
+                appUser.setLast_login_time(last_login_time);
+                appUserDao.insert(appUser);
+            } else {
+                // [Update] 更新时间
+                appUser.setLast_login_uuid(uuid);
+                appUser.setLast_login_ip(last_login_ip);
+                appUser.setLast_login_time(last_login_time);
+                appUserDao.updateById(appUser);
+            }
+            appUser.setUser_id(appUser.getId());
+            appUser.setSession_key(session_key);
+
+            // 登录成功
+            return loginSuccess(appUser);
+        } else {
+            throw new CustException(response.getStr("errmsg"));
+        }
+
+    }
+
+}

+ 37 - 0
src/main/java/com/backendsys/modules/sdk/wechat/miniprogram/utils/WechatUtil.java

@@ -0,0 +1,37 @@
+package com.backendsys.modules.sdk.wechat.miniprogram.utils;
+
+import cn.hutool.http.HttpUtil;
+import cn.hutool.json.JSONObject;
+import cn.hutool.json.JSONUtil;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+@Component
+public class WechatUtil {
+
+    @Value("${wechat.miniprogram.appid}")
+    private String APPID;
+    @Value("${wechat.miniprogram.app-secret}")
+    private String APP_SECRET;
+
+    /**
+     *
+     * 微信 - 小程序登录
+     * https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/user-login/code2Session.html
+     * 返回值:{ "openid":"xxxxxx", "session_key":"xxxxx", "unionid":"xxxxx", "errcode":0, "errmsg":"xxxxx" }
+     */
+    public JSONObject getCode2Session(String code) {
+        String url = "https://api.weixin.qq.com/sns/jscode2session";
+        Map<String, Object> params = new LinkedHashMap<>();
+        params.put("appid", APPID);
+        params.put("secret", APP_SECRET);
+        params.put("js_code", code);
+        params.put("grant_type", "authorization_code");
+        String result = HttpUtil.post(url, params);
+        return JSONUtil.parseObj(result);
+    }
+
+}

+ 9 - 1
src/main/resources/application-local.yml

@@ -168,4 +168,12 @@ ai:
 
 deepseek:
   url: https://api.deepseek.com
-  api-key: sk-6078a4ceee5443128ce74a23538dd54e
+  api-key: sk-6078a4ceee5443128ce74a23538dd54e
+
+# 微信
+wechat:
+  miniprogram:
+#     appid: wx159b7ad079a34fa1
+#     app-secret: 7671bcb556dd8a48052f1f669d70b643
+     appid: wx3c789d9920a0f98f
+     app-secret: c713a96ac6df5abb9b7edb80fdc41e8b

+ 7 - 1
src/main/resources/application-prod.yml

@@ -160,4 +160,10 @@ ai:
 
 deepseek:
   url: https://api.deepseek.com
-  api-key: sk-6078a4ceee5443128ce74a23538dd54e
+  api-key: sk-6078a4ceee5443128ce74a23538dd54e
+
+# 微信
+wechat:
+  miniprogram:
+    appid: wx3c789d9920a0f98f
+    app-secret: c713a96ac6df5abb9b7edb80fdc41e8b