mirror of
https://gitee.com/huangge1199_admin/vue-pro.git
synced 2025-01-19 03:30:06 +08:00
saas:支持社交应用的多租户配置(mp)
This commit is contained in:
parent
d256275099
commit
6f757e5297
@ -59,3 +59,8 @@ tenant-id: {{appTenentId}}
|
||||
POST {{appApi}}/member/auth/refresh-token?refreshToken=bc43d929094849a28b3a69f6e6940d70
|
||||
Content-Type: application/json
|
||||
tenant-id: {{appTenentId}}
|
||||
|
||||
### 请求 /auth/create-weixin-jsapi-signature 接口 => 成功
|
||||
POST {{appApi}}/member/auth/create-weixin-jsapi-signature?url=http://www.iocoder.cn
|
||||
Authorization: Bearer {{appToken}}
|
||||
tenant-id: {{appTenentId}}
|
||||
|
@ -1,12 +1,16 @@
|
||||
package cn.iocoder.yudao.module.member.controller.app.auth;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
|
||||
import cn.iocoder.yudao.framework.security.config.SecurityProperties;
|
||||
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
|
||||
import cn.iocoder.yudao.module.member.controller.app.auth.vo.*;
|
||||
import cn.iocoder.yudao.module.member.convert.auth.AuthConvert;
|
||||
import cn.iocoder.yudao.module.member.service.auth.MemberAuthService;
|
||||
import cn.iocoder.yudao.module.system.api.social.SocialClientApi;
|
||||
import cn.iocoder.yudao.module.system.api.social.dto.SocialWxJsapiSignatureRespDTO;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.Parameters;
|
||||
@ -33,6 +37,9 @@ public class AppAuthController {
|
||||
@Resource
|
||||
private MemberAuthService authService;
|
||||
|
||||
@Resource
|
||||
private SocialClientApi socialClientApi;
|
||||
|
||||
@Resource
|
||||
private SecurityProperties securityProperties;
|
||||
|
||||
@ -109,4 +116,13 @@ public class AppAuthController {
|
||||
return success(authService.weixinMiniAppLogin(reqVO));
|
||||
}
|
||||
|
||||
@PostMapping("/create-weixin-jsapi-signature")
|
||||
@Operation(summary = "创建微信 JS SDK 初始化所需的签名",
|
||||
description = "参考 https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html 文档")
|
||||
public CommonResult<SocialWxJsapiSignatureRespDTO> createWeixinMpJsapiSignature(@RequestParam("url") String url) {
|
||||
SocialWxJsapiSignatureRespDTO signature = socialClientApi.createWxMpJsapiSignature(
|
||||
UserTypeEnum.MEMBER.getValue(), url);
|
||||
return success(AuthConvert.INSTANCE.convert(signature));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,31 @@
|
||||
package cn.iocoder.yudao.module.member.controller.app.auth.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Schema(description = "用户 APP - 微信公众号 JSAPI 签名 Response VO")
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class AuthWeixinJsapiSignatureRespVO {
|
||||
|
||||
@Schema(description = "微信公众号的 appId", requiredMode = Schema.RequiredMode.REQUIRED, example = "hello")
|
||||
private String appId;
|
||||
|
||||
@Schema(description = "匿名串", requiredMode = Schema.RequiredMode.REQUIRED, example = "world")
|
||||
private String nonceStr;
|
||||
|
||||
@Schema(description = "时间戳", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
private Long timestamp;
|
||||
|
||||
@Schema(description = "URL", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn")
|
||||
private String url;
|
||||
|
||||
@Schema(description = "签名", requiredMode = Schema.RequiredMode.REQUIRED, example = "阿巴阿巴")
|
||||
private String signature;
|
||||
|
||||
}
|
@ -9,6 +9,7 @@ import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeUseReqDTO;
|
||||
import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeValidateReqDTO;
|
||||
import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO;
|
||||
import cn.iocoder.yudao.module.system.api.social.dto.SocialUserUnbindReqDTO;
|
||||
import cn.iocoder.yudao.module.system.api.social.dto.SocialWxJsapiSignatureRespDTO;
|
||||
import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
@ -29,4 +30,6 @@ public interface AuthConvert {
|
||||
|
||||
SmsCodeValidateReqDTO convert(AppAuthSmsValidateReqVO bean);
|
||||
|
||||
SocialWxJsapiSignatureRespDTO convert(SocialWxJsapiSignatureRespDTO bean);
|
||||
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
package cn.iocoder.yudao.module.system.api.social;
|
||||
|
||||
import cn.iocoder.yudao.module.system.api.social.dto.SocialWxJsapiSignatureRespDTO;
|
||||
import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum;
|
||||
|
||||
/**
|
||||
@ -19,4 +20,13 @@ public interface SocialClientApi {
|
||||
*/
|
||||
String getAuthorizeUrl(Integer type, Integer userType, String redirectUri);
|
||||
|
||||
/**
|
||||
* 创建微信 JS SDK 初始化所需的签名
|
||||
*
|
||||
* @param userType 用户类型
|
||||
* @param url 访问的 URL 地址
|
||||
* @return 签名
|
||||
*/
|
||||
SocialWxJsapiSignatureRespDTO createWxMpJsapiSignature(Integer userType, String url);
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,34 @@
|
||||
package cn.iocoder.yudao.module.system.api.social.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 微信公众号 JSAPI 签名 Response DTO
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Data
|
||||
public class SocialWxJsapiSignatureRespDTO {
|
||||
|
||||
/**
|
||||
* 微信公众号的 appId
|
||||
*/
|
||||
private String appId;
|
||||
/**
|
||||
* 匿名串
|
||||
*/
|
||||
private String nonceStr;
|
||||
/**
|
||||
* 时间戳
|
||||
*/
|
||||
private Long timestamp;
|
||||
/**
|
||||
* URL
|
||||
*/
|
||||
private String url;
|
||||
/**
|
||||
* 签名
|
||||
*/
|
||||
private String signature;
|
||||
|
||||
}
|
@ -1,6 +1,9 @@
|
||||
package cn.iocoder.yudao.module.system.api.social;
|
||||
|
||||
import cn.iocoder.yudao.module.system.api.social.dto.SocialWxJsapiSignatureRespDTO;
|
||||
import cn.iocoder.yudao.module.system.convert.social.SocialClientConvert;
|
||||
import cn.iocoder.yudao.module.system.service.social.SocialClientService;
|
||||
import me.chanjar.weixin.common.bean.WxJsapiSignature;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
@ -23,4 +26,10 @@ public class SocialClientApiImpl implements SocialClientApi {
|
||||
return socialClientService.getAuthorizeUrl(type, userType, redirectUri);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SocialWxJsapiSignatureRespDTO createWxMpJsapiSignature(Integer userType, String url) {
|
||||
WxJsapiSignature signature = socialClientService.createWxMpJsapiSignature(userType, url);
|
||||
return SocialClientConvert.INSTANCE.convert(signature);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,4 +0,0 @@
|
||||
/**
|
||||
* 占位,避免 package 无法提交到 Git 仓库
|
||||
*/
|
||||
package cn.iocoder.yudao.module.system.controller.app;
|
@ -1,4 +0,0 @@
|
||||
### 请求 /login 接口 => 成功
|
||||
POST {{appApi}}/system/wx-mp/create-jsapi-signature?url=http://www.iocoder.cn
|
||||
Authorization: Bearer {{appToken}}
|
||||
tenant-id: {{appTenentId}}
|
@ -1,38 +0,0 @@
|
||||
package cn.iocoder.yudao.module.system.controller.app.weixin;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import me.chanjar.weixin.common.bean.WxJsapiSignature;
|
||||
import me.chanjar.weixin.common.error.WxErrorException;
|
||||
import me.chanjar.weixin.mp.api.WxMpService;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
|
||||
@Tag(name = "微信公众号")
|
||||
@RestController
|
||||
@RequestMapping("/system/wx-mp")
|
||||
@Validated
|
||||
@Slf4j
|
||||
public class AppWxMpController {
|
||||
|
||||
@Resource
|
||||
private WxMpService mpService;
|
||||
|
||||
// TODO @芋艿:需要额外考虑个问题;多租户下,如果每个小程序一个微信公众号,则会存在多个 appid;
|
||||
@PostMapping("/create-jsapi-signature")
|
||||
@Operation(summary = "创建微信 JS SDK 初始化所需的签名",
|
||||
description = "参考 https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html 文档")
|
||||
public CommonResult<WxJsapiSignature> createJsapiSignature(@RequestParam("url") String url) throws WxErrorException {
|
||||
return success(mpService.createJsapiSignature(url));
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package cn.iocoder.yudao.module.system.convert.social;
|
||||
|
||||
import cn.iocoder.yudao.module.system.api.social.dto.SocialWxJsapiSignatureRespDTO;
|
||||
import me.chanjar.weixin.common.bean.WxJsapiSignature;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
@Mapper
|
||||
public interface SocialClientConvert {
|
||||
|
||||
SocialClientConvert INSTANCE = Mappers.getMapper(SocialClientConvert.class);
|
||||
|
||||
SocialWxJsapiSignatureRespDTO convert(WxJsapiSignature bean);
|
||||
|
||||
}
|
@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.system.service.social;
|
||||
|
||||
import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum;
|
||||
import com.xingyuv.jushauth.model.AuthUser;
|
||||
import me.chanjar.weixin.common.bean.WxJsapiSignature;
|
||||
|
||||
/**
|
||||
* 社交应用 Service 接口
|
||||
@ -31,4 +32,13 @@ public interface SocialClientService {
|
||||
*/
|
||||
AuthUser getAuthUser(Integer socialType, Integer userType, String code, String state);
|
||||
|
||||
/**
|
||||
* 创建微信 JS SDK 初始化所需的签名
|
||||
*
|
||||
* @param userType 用户类型
|
||||
* @param url 访问的 URL 地址
|
||||
* @return 签名
|
||||
*/
|
||||
WxJsapiSignature createWxMpJsapiSignature(Integer userType, String url);
|
||||
|
||||
}
|
||||
|
@ -4,21 +4,33 @@ import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.framework.common.util.cache.CacheUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.http.HttpUtils;
|
||||
import cn.iocoder.yudao.framework.social.core.YudaoAuthRequestFactory;
|
||||
import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialClientDO;
|
||||
import cn.iocoder.yudao.module.system.dal.mysql.social.SocialClientMapper;
|
||||
import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum;
|
||||
import com.binarywang.spring.starter.wxjava.mp.properties.WxMpProperties;
|
||||
import com.google.common.cache.CacheLoader;
|
||||
import com.google.common.cache.LoadingCache;
|
||||
import com.xingyuv.jushauth.config.AuthConfig;
|
||||
import com.xingyuv.jushauth.model.AuthCallback;
|
||||
import com.xingyuv.jushauth.model.AuthResponse;
|
||||
import com.xingyuv.jushauth.model.AuthUser;
|
||||
import com.xingyuv.jushauth.request.AuthRequest;
|
||||
import com.xingyuv.jushauth.utils.AuthStateUtils;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import me.chanjar.weixin.common.bean.WxJsapiSignature;
|
||||
import me.chanjar.weixin.common.redis.RedisTemplateWxRedisOps;
|
||||
import me.chanjar.weixin.mp.api.WxMpService;
|
||||
import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl;
|
||||
import me.chanjar.weixin.mp.config.impl.WxMpRedisConfigImpl;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.time.Duration;
|
||||
import java.util.Objects;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
@ -37,6 +49,32 @@ public class SocialClientServiceImpl implements SocialClientService {
|
||||
@Resource // 由于自定义了 YudaoAuthRequestFactory 无法覆盖默认的 AuthRequestFactory,所以只能注入它
|
||||
private YudaoAuthRequestFactory yudaoAuthRequestFactory;
|
||||
|
||||
@Resource
|
||||
private WxMpService mpService;
|
||||
@Resource
|
||||
private StringRedisTemplate stringRedisTemplate; // WxMpService 需要使用到,所以在 Service 注入了它
|
||||
@Resource
|
||||
private WxMpProperties mpProperties;
|
||||
/**
|
||||
* 缓存 WxMpService 对象
|
||||
*
|
||||
* key:使用微信公众号的 appId + secret 拼接,即 {@link SocialClientDO} 的 clientId 和 clientSecret 属性。
|
||||
* 为什么 key 使用这种格式?因为 {@link SocialClientDO} 在管理后台可以变更,通过这个 key 存储它的单例。
|
||||
*
|
||||
* 为什么要做 WxMpService 缓存?因为 WxMpService 构建成本比较大,所以尽量保证它是单例。
|
||||
*/
|
||||
private final LoadingCache<String, WxMpService> mpServiceCache = CacheUtils.buildAsyncReloadingCache(
|
||||
Duration.ofSeconds(10L),
|
||||
new CacheLoader<String, WxMpService>() {
|
||||
|
||||
@Override
|
||||
public WxMpService load(String key) {
|
||||
String[] keys = key.split(":");
|
||||
return buildMpService(keys[0], keys[1]);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
@Resource
|
||||
private SocialClientMapper socialClientMapper;
|
||||
|
||||
@ -91,4 +129,49 @@ public class SocialClientServiceImpl implements SocialClientService {
|
||||
return request;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public WxJsapiSignature createWxMpJsapiSignature(Integer userType, String url) {
|
||||
WxMpService mpService = buildMpService(userType);
|
||||
return mpService.createJsapiSignature(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建 clientId + clientSecret 对应的 WxMpService 对象
|
||||
*
|
||||
* @param userType 用户类型
|
||||
* @return WxMpService 对象
|
||||
*/
|
||||
private WxMpService buildMpService(Integer userType) {
|
||||
// 第一步,查询 DB 的配置项,获得对应的 WxMpService 对象
|
||||
SocialClientDO client = socialClientMapper.selectBySocialTypeAndUserType(
|
||||
SocialTypeEnum.WECHAT_MP.getType(), userType);
|
||||
if (client != null && Objects.equals(client.getStatus(), CommonStatusEnum.ENABLE.getStatus())) {
|
||||
return mpServiceCache.getUnchecked(client.getClientId() + ":" + client.getClientSecret());
|
||||
}
|
||||
// 第二步,不存在 DB 配置项,则使用 application-*.yaml 对应的 WxMpService 对象
|
||||
return mpService;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建 clientId + clientSecret 对应的 WxMpService 对象
|
||||
*
|
||||
* @param clientId 微信公众号 appId
|
||||
* @param clientSecret 微信公众号 secret
|
||||
* @return WxMpService 对象
|
||||
*/
|
||||
private WxMpService buildMpService(String clientId, String clientSecret) {
|
||||
// 第一步,创建 WxMpRedisConfigImpl 对象
|
||||
WxMpRedisConfigImpl configStorage = new WxMpRedisConfigImpl(
|
||||
new RedisTemplateWxRedisOps(stringRedisTemplate),
|
||||
mpProperties.getConfigStorage().getKeyPrefix());
|
||||
configStorage.setAppId(clientId);
|
||||
configStorage.setSecret(clientSecret);
|
||||
|
||||
// 第二步,创建 WxMpService 对象
|
||||
WxMpService service = new WxMpServiceImpl();
|
||||
service.setWxMpConfigStorage(configStorage);
|
||||
return service;
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user