This commit is contained in:
2025-10-24 17:12:18 +08:00
commit 9dead7a890
2004 changed files with 298646 additions and 0 deletions

View File

@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>com.pxdj</groupId>
<artifactId>pxdj-security</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>pxdj-security-api</artifactId>
<description>商城安全模块接口部分</description>
<dependencies>
<dependency>
<groupId>com.pxdj</groupId>
<artifactId>pxdj-security-common</artifactId>
<version>${pxdj.version}</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.9.0</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.56</version>
</dependency>
<dependency>
<groupId>net.sf.json-lib</groupId>
<artifactId>json-lib</artifactId>
<version>2.4</version>
<classifier>jdk15</classifier>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,22 @@
package com.pxdj.security.api.adapter;
import com.pxdj.common.adapter.DefaultAuthConfigAdapter;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
import java.util.Collections;
import java.util.List;
/**
* @author 菠萝凤梨
* @date 2022/3/28 15:17
*/
@Primary
@Component("apiResourceServerAdapter")
public class ResourceServerAdapter extends DefaultAuthConfigAdapter {
@Override
public List<String> pathPatterns() {
return Collections.singletonList("/p/*");
}
}

View File

@@ -0,0 +1,81 @@
/*
package com.yami.shop.security.api.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.yami.shop.bean.model.User;
import com.yami.shop.common.exception.YamiShopBindException;
import com.yami.shop.common.util.PrincipalUtil;
import com.yami.shop.dao.UserMapper;
import com.yami.shop.security.common.bo.UserInfoInTokenBO;
import com.yami.shop.security.common.dto.AuthenticationDTO;
import com.yami.shop.security.common.enums.SysTypeEnum;
import com.yami.shop.security.common.manager.PasswordCheckManager;
import com.yami.shop.security.common.manager.PasswordManager;
import com.yami.shop.security.common.manager.TokenStore;
import com.yami.shop.security.common.vo.TokenInfoVO;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import org.springframework.beans.factory.annotation.Autowired;
import com.yami.shop.common.response.ServerResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import jakarta.validation.Valid;
*/
/**
* @author 菠萝凤梨
* @date 2022/3/28 15:20
*//*
@RestController
@Tag(name = "登录")
public class LoginController {
@Autowired
private TokenStore tokenStore;
@Autowired
private UserMapper userMapper;
@Autowired
private PasswordCheckManager passwordCheckManager;
@Autowired
private PasswordManager passwordManager;
@PostMapping("/login")
@Operation(summary = "账号密码(用于前端登录)" , description = "通过账号/手机号/用户名密码登录,还要携带用户的类型,也就是用户所在的系统")
public ServerResponseEntity<TokenInfoVO> login(
@Valid @RequestBody AuthenticationDTO authenticationDTO) {
String mobileOrUserName = authenticationDTO.getUserName();
User user = getUser(mobileOrUserName);
String decryptPassword = passwordManager.decryptPassword(authenticationDTO.getPassWord());
// 半小时内密码输入错误十次已限制登录30分钟
passwordCheckManager.checkPassword(SysTypeEnum.ORDINARY,authenticationDTO.getUserName(), decryptPassword, user.getLoginPassword());
UserInfoInTokenBO userInfoInToken = new UserInfoInTokenBO();
userInfoInToken.setUserId(user.getUserId());
userInfoInToken.setSysType(SysTypeEnum.ORDINARY.value());
userInfoInToken.setEnabled(user.getStatus() == 1);
// 存储token返回vo
TokenInfoVO tokenInfoVO = tokenStore.storeAndGetVo(userInfoInToken);
return ServerResponseEntity.success(tokenInfoVO);
}
private User getUser(String mobileOrUserName) {
User user = null;
// 手机验证码登陆,或传过来的账号很像手机号
if (PrincipalUtil.isMobile(mobileOrUserName)) {
user = userMapper.selectOne(new LambdaQueryWrapper<User>().eq(User::getUserMobile, mobileOrUserName));
}
// 如果不是手机验证码登陆, 找不到手机号就找用户名
if (user == null) {
user = userMapper.selectOneByUserName(mobileOrUserName);
}
if (user == null) {
throw new YamiShopBindException("账号或密码不正确");
}
return user;
}
}
*/

View File

@@ -0,0 +1,53 @@
package com.pxdj.security.api.manager;
import java.util.TimerTask;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* 异步任务管理器
*
* @author ruoyi
*/
public class AsyncManager
{
/**
* 操作延迟10毫秒
*/
private final int OPERATE_DELAY_TIME = 10;
/**
* 异步操作任务调度线程池
*/
private ScheduledExecutorService executor = SpringsUtils.getBean("scheduledExecutorService");
/**
* 单例模式
*/
private AsyncManager(){}
private static AsyncManager me = new AsyncManager();
public static AsyncManager me()
{
return me;
}
/**
* 执行任务
*
* @param task 任务
*/
public void execute(TimerTask task)
{
executor.schedule(task, OPERATE_DELAY_TIME, TimeUnit.MILLISECONDS);
}
/**
* 停止任务线程池
*/
public void shutdown()
{
Threads.shutdownAndAwaitTermination(executor);
}
}

View File

@@ -0,0 +1,162 @@
package com.pxdj.security.api.manager;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import java.util.Optional;
/**
* spring工具类 方便在非spring管理环境中获取bean
*
* @author ruoyi
*/
@Component
public final class SpringsUtils implements BeanFactoryPostProcessor, ApplicationContextAware
{
/** Spring应用上下文环境 */
private static ConfigurableListableBeanFactory beanFactory;
private static ApplicationContext applicationContext;
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException
{
SpringsUtils.beanFactory = beanFactory;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException
{
SpringsUtils.applicationContext = applicationContext;
}
/**
* 获取对象
*
* @param name
* @return Object 一个以所给名字注册的bean的实例
* @throws org.springframework.beans.BeansException
*
*/
@SuppressWarnings("unchecked")
public static <T> T getBean(String name) throws BeansException
{
return (T) beanFactory.getBean(name);
}
/**
* 获取类型为requiredType的对象
*
* @param clz
* @return
* @throws org.springframework.beans.BeansException
*
*/
public static <T> T getBean(Class<T> clz) throws BeansException
{
T result = (T) beanFactory.getBean(clz);
return result;
}
/**
* 如果BeanFactory包含一个与所给名称匹配的bean定义则返回true
*
* @param name
* @return boolean
*/
public static boolean containsBean(String name)
{
return beanFactory.containsBean(name);
}
/**
* 判断以给定名字注册的bean定义是一个singleton还是一个prototype。 如果与给定名字相应的bean定义没有被找到将会抛出一个异常NoSuchBeanDefinitionException
*
* @param name
* @return boolean
* @throws org.springframework.beans.factory.NoSuchBeanDefinitionException
*
*/
public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException
{
return beanFactory.isSingleton(name);
}
/**
* @param name
* @return Class 注册对象的类型
* @throws org.springframework.beans.factory.NoSuchBeanDefinitionException
*
*/
public static Class<?> getType(String name) throws NoSuchBeanDefinitionException
{
return beanFactory.getType(name);
}
/**
* 如果给定的bean名字在bean定义中有别名则返回这些别名
*
* @param name
* @return
* @throws org.springframework.beans.factory.NoSuchBeanDefinitionException
*
*/
public static String[] getAliases(String name) throws NoSuchBeanDefinitionException
{
return beanFactory.getAliases(name);
}
/**
* 获取aop代理对象
*
* @param invoker
* @return
*/
@SuppressWarnings("unchecked")
public static <T> T getAopProxy(T invoker)
{
return (T) AopContext.currentProxy();
}
/**
* 获取当前的环境配置无配置返回null
*
* @return 当前的环境配置
*/
public static String[] getActiveProfiles()
{
return applicationContext.getEnvironment().getActiveProfiles();
}
/**
* 获取当前的环境配置,当有多个环境配置时,只获取第一个
*
* @return 当前的环境配置
*/
public static String getActiveProfile()
{
final String[] activeProfiles = getActiveProfiles();
return Optional.ofNullable(activeProfiles)
.filter(profiles -> profiles.length > 0)
.map(profiles -> profiles[0])
.orElse(null);
}
/**
* 获取配置文件中的值
*
* @param key 配置文件的key
* @return 当前的配置文件的值
*
*/
public static String getRequiredProperty(String key)
{
return applicationContext.getEnvironment().getRequiredProperty(key);
}
}

View File

@@ -0,0 +1,99 @@
package com.pxdj.security.api.manager;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 线程相关工具类.
*
* @author ruoyi
*/
public class Threads
{
private static final Logger logger = LoggerFactory.getLogger(Threads.class);
/**
* sleep等待,单位为毫秒
*/
public static void sleep(long milliseconds)
{
try
{
Thread.sleep(milliseconds);
}
catch (InterruptedException e)
{
return;
}
}
/**
* 停止线程池
* 先使用shutdown, 停止接收新任务并尝试完成所有已存在任务.
* 如果超时, 则调用shutdownNow, 取消在workQueue中Pending的任务,并中断所有阻塞函数.
* 如果仍然超時,則強制退出.
* 另对在shutdown时线程本身被调用中断做了处理.
*/
public static void shutdownAndAwaitTermination(ExecutorService pool)
{
if (pool != null && !pool.isShutdown())
{
pool.shutdown();
try
{
if (!pool.awaitTermination(120, TimeUnit.SECONDS))
{
pool.shutdownNow();
if (!pool.awaitTermination(120, TimeUnit.SECONDS))
{
logger.info("Pool did not terminate");
}
}
}
catch (InterruptedException ie)
{
pool.shutdownNow();
Thread.currentThread().interrupt();
}
}
}
/**
* 打印线程异常信息
*/
public static void printException(Runnable r, Throwable t)
{
if (t == null && r instanceof Future<?>)
{
try
{
Future<?> future = (Future<?>) r;
if (future.isDone())
{
future.get();
}
}
catch (CancellationException ce)
{
t = ce;
}
catch (ExecutionException ee)
{
t = ee.getCause();
}
catch (InterruptedException ie)
{
Thread.currentThread().interrupt();
}
}
if (t != null)
{
logger.error(t.getMessage(), t);
}
}
}

View File

@@ -0,0 +1,21 @@
package com.pxdj.security.api.model;
import lombok.Data;
import javax.validation.constraints.NotBlank;
@Data
public class Phone {
private String encryptedData;
private String iv;
private String code;
private String openid;
private String sessionKey;
private String inviteCode;
private String sessionId;
private String unionid;
//小程序登陆时返回的code(使用code登陆必填)
private String openIdCode;
private String phoneCode;
}

View File

@@ -0,0 +1,11 @@
package com.pxdj.security.api.model;
import lombok.Data;
@Data
public class WxInfo {
private String session_key;
private String openid;
private String unionid;
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
*
* https://www.mall4j.com/
*
* 未经允许,不可做商业用途!
*
* 版权所有,侵权必究!
*/
package com.pxdj.security.api.model;
import lombok.Data;
/**
* 用户详细信息
* @author LGH
*/
@Data
public class YamiUser {
/**
* 用户ID
*/
private String userId;
private String bizUserId;
private Boolean enabled;
/**
* 自提点Id
*/
private Long stationId;
/**
* 店铺Id
*/
private Long shopId;
/**
* 微信opendid
*/
private String openid;
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright (c) 2018-2999 广州市蓝海创新科技有限公司 All rights reserved.
*
* https://www.mall4j.com/
*
* 未经允许,不可做商业用途!
*
* 版权所有,侵权必究!
*/
package com.pxdj.security.api.util;
import com.pxdj.security.api.model.YamiUser;
import com.pxdj.common.bo.UserInfoInTokenBO;
import com.pxdj.common.util.AuthUserContext;
import lombok.experimental.UtilityClass;
/**
* @author LGH
*/
@UtilityClass
public class SecurityUtils {
private static final String USER_REQUEST = "/p/";
/**
* 获取用户
*/
public YamiUser getUser() {
/*if (!HttpContextUtils.getHttpServletRequest().getRequestURI().startsWith(USER_REQUEST)) {
// 用户相关的请求,应该以/p开头
throw new RuntimeException("yami.user.request.error");
}*/
UserInfoInTokenBO userInfoInTokenBO = AuthUserContext.get();
YamiUser yamiUser = new YamiUser();
yamiUser.setUserId(userInfoInTokenBO.getUserId());
yamiUser.setBizUserId(userInfoInTokenBO.getBizUserId());
yamiUser.setEnabled(userInfoInTokenBO.getEnabled());
yamiUser.setShopId(userInfoInTokenBO.getShopId());
yamiUser.setStationId(userInfoInTokenBO.getOtherId());
yamiUser.setOpenid(userInfoInTokenBO.getOpenid());
return yamiUser;
}
}

View File

@@ -0,0 +1,72 @@
package com.pxdj.security.api.wx;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.*;
/**
* AES加密
* @author liuyazhuang
*
*/
public class AES {
public static boolean initialized = false;
/**
* AES解密
*
* @param content
* 密文
* @return
* @throws InvalidAlgorithmParameterException
* @throws NoSuchProviderException
*/
public byte[] decrypt(byte[] content, byte[] keyByte, byte[] ivByte) throws InvalidAlgorithmParameterException {
initialize();
try {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
Key sKeySpec = new SecretKeySpec(keyByte, "AES");
cipher.init(Cipher.DECRYPT_MODE, sKeySpec, generateIV(ivByte));// 初始化
byte[] result = cipher.doFinal(content);
return result;
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
} catch (NoSuchProviderException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
public static void initialize() {
if (initialized)
return;
Security.addProvider(new BouncyCastleProvider());
initialized = true;
}
// 生成iv
public static AlgorithmParameters generateIV(byte[] iv) throws Exception {
AlgorithmParameters params = AlgorithmParameters.getInstance("AES");
params.init(new IvParameterSpec(iv));
return params;
}
}

View File

@@ -0,0 +1,412 @@
package com.pxdj.security.api.wx;
import cn.hutool.core.util.IdUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.pxdj.api.domain.User;
import com.pxdj.api.service.UserService;
import com.pxdj.common.bo.UserInfoInTokenBO;
import com.pxdj.common.enums.SysTypeEnum;
import com.pxdj.common.manager.PasswordManager;
import com.pxdj.common.manager.TokenStore;
import com.pxdj.common.response.ServerResponseEntity;
import com.pxdj.common.utils.RedisUtil;
import com.pxdj.common.utils.WeiXinService;
import com.pxdj.common.utils.WeiXinUtil;
import com.pxdj.common.vo.TokenInfoVO;
import com.pxdj.security.api.model.Phone;
import com.pxdj.security.api.model.WxInfo;
import lombok.AllArgsConstructor;
import net.sf.json.JSONObject;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.apache.commons.codec.binary.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Date;
import java.util.Map;
import java.util.Random;
/*import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo;
import cn.binarywang.wx.miniapp.util.WxMaConfigHolder;*/
import static com.pxdj.common.utils.SpringContextUtils.applicationContext;
//import static com.yami.shop.common.util.SpringContextUtils.applicationContext;
/**
* 封装对外访问方法
*
* @author liuyazhuang
*/
@RestController
@RequestMapping("/wxcore")
@AllArgsConstructor
public class WXCore {
private static final Logger log = LoggerFactory.getLogger(WXCore.class);
@Autowired
private TokenStore tokenStore;
@Autowired
private UserService userService;
private final PasswordManager passwordManager;
private final PasswordEncoder passwordEncoder;
private static final String WATERMARK = "watermark";
private static final String APPID = "appid";
@Autowired
private WeiXinService weiXinService;
@Autowired
private WeiXinUtil weiXinUtil;
public static void printAaa() {
System.out.println();
}
/**
* 模拟登录
*
* @return
*/
@PostMapping("rLogin")
public ServerResponseEntity<TokenInfoVO> testLogin(@RequestBody User user) {
User one = userService.lambdaQuery().eq(User::getUserMobile, user.getUserMobile())
.eq(User::getUserType, 1).one();
if (one == null) {
return ServerResponseEntity.showFailMsg("手机号不存在");
}
if (user.getSessionId() != null && !user.getSessionId().isEmpty()) {
try {
String distanceKey = "user_merchant_distance:" + user.getSessionId();
Map<String, String> distanceMap = RedisUtil.get(distanceKey);
if (distanceMap != null) {
String newDistanceKey = "user_merchant_distance:" + user.getUserId();
RedisUtil.set(newDistanceKey, distanceMap);
// RedisUtil.del(locationKey);
// RedisUtil.del(distanceKey);
log.info("用户登录成功缓存数据从sessionId[{}]迁移到userId[{}]",
user.getSessionId(), user.getUserId());
}
} catch (Exception e) {
log.error("迁移位置缓存数据失败", e);
}
}
UserInfoInTokenBO userInfoInToken = new UserInfoInTokenBO();
userInfoInToken.setUserId(one.getUserId());
userInfoInToken.setSysType(SysTypeEnum.ORDINARY.value());
userInfoInToken.setEnabled(true);
// 存储token返回vo
TokenInfoVO tokenInfoVO = tokenStore.storeAndGetVo(userInfoInToken);
return ServerResponseEntity.success(tokenInfoVO);
}
@PostMapping("merchantLogin")
public ServerResponseEntity<TokenInfoVO> testtMerchantLogin(@RequestBody User user) {
User one = userService.lambdaQuery().eq(User::getUserMobile, user.getUserMobile())
.eq(User::getUserType, 2).one();
if (one == null) {
return ServerResponseEntity.showFailMsg("手机号不存在");
}
UserInfoInTokenBO userInfoInToken = new UserInfoInTokenBO();
userInfoInToken.setUserId(one.getUserId());
userInfoInToken.setSysType(SysTypeEnum.ORDINARY.value());
userInfoInToken.setEnabled(true);
// 存储token返回vo
TokenInfoVO tokenInfoVO = tokenStore.storeAndGetVo(userInfoInToken);
return ServerResponseEntity.success(tokenInfoVO);
}
/*
*//**
* 小程序授权登录
*
* @param loginParam
* @return
*//*
@PostMapping("/login")
@SneakyThrows
public ServerResponseEntity<TokenInfoBO> login(@RequestBody LoginParam loginParam) {
//根据appId获取登录类型
AppIdEnum appIdEnum = AppIdEnum.getByAppId(loginParam.getAppId());
Assert.isFalse(appIdEnum == null, "appId有误");
//根据传入的appId进行切换ma
wxMaService.switchoverTo(loginParam.getAppId());
//获取用户的openId
WxMaJscode2SessionResult session = wxMaService.getUserService().getSessionInfo(loginParam.getOpenIdCode());
WxMaPhoneNumberInfo phoneNoInfo = wxMaService.getUserService().getPhoneNumber(loginParam.getPhoneCode());
WxMaConfigHolder.remove();//清理ThreadLocal
String openId = session.getUnionid();
//根据openId 获取用户 --
User user = userService.lambdaQuery().eq(User::getOpenid, openId).eq(User::getUserType, appIdEnum.getSys()).one();
//商户登录 若用户为空则不允许登录
Assert.isFalse(appIdEnum.getSys() == AppIdEnum.MERCHANT.getSys() && user == null, "请先入住商户,审核通过后再进行登录");
if (user == null) {
Date now = new Date();
userService.save(new User()
.setModifyTime(now)
.setUnionid(session.getUnionid())
.setOpenid(openId)
.setUserId(snowflake.nextIdStr())
.setUserRegtime(now)
.setUserType(appIdEnum.getSys())
.setStatus(YesOrNoEnum.YES.getCode())
.setUserMobile(phoneNoInfo.getPhoneNumber())
);
}
// 4. 登录成功
return ServerResponseEntity.success(tokenStore.storeAndGetVo(new MiniAppUser().setUserId(user.getUserId()).setOpenid(openId).setUnionid(session.getUnionid()).setSys(appIdEnum.getSys())
));
}*/
/**
* 解密数据
*
* @return
* @throws Exception
*/
@PostMapping("/wxLogin")
@Transactional
public ServerResponseEntity<TokenInfoVO> decrypt(@RequestBody Phone phone) {
String result = "";
Environment environment = applicationContext.getEnvironment();
String appId = environment.getProperty("weixin.appid");
String secret = environment.getProperty("weixin.secret");
TokenInfoVO tokenInfoVO = new TokenInfoVO();
WxInfo wxInfo = getWeChatOpenId(phone.getOpenIdCode());
User user = userService.selectUserByOpenId(wxInfo.getOpenid());
if (user == null) {
String accessToken = weiXinUtil.postToken(appId, secret);
String tel = weiXinService.getUserPhoneNumber(accessToken, phone.getPhoneCode());
user = new User();
String userId = IdUtil.simpleUUID();
Date now = new Date();
user.setModifyTime(now);
user.setUserRegtime(now);
user.setUserMobile(tel);
user.setOpenid(wxInfo.getOpenid());
user.setUnionid(wxInfo.getUnionid());
user.setUserId(userId);
user.setStatus(1);
userService.save(user);
} else {
user.setUnionid(wxInfo.getUnionid());
userService.updateById(user);
}
if (phone.getSessionId() != null && !phone.getSessionId().isEmpty()) {
try {
String distanceKey = "user_merchant_distance:" + phone.getSessionId();
Map<String, String> distanceMap = RedisUtil.get(distanceKey);
if (distanceMap != null) {
String newDistanceKey = "user_merchant_distance:" + user.getUserId();
RedisUtil.set(newDistanceKey, distanceMap);
// RedisUtil.del(locationKey);
// RedisUtil.del(distanceKey);
log.info("用户登录成功缓存数据从sessionId[{}]迁移到userId[{}]",
user.getSessionId(), user.getUserId());
}
} catch (Exception e) {
log.error("迁移位置缓存数据失败", e);
}
}
try {
UserInfoInTokenBO userInfoInToken = new UserInfoInTokenBO();
userInfoInToken.setUserId(user.getUserId());
userInfoInToken.setSysType(SysTypeEnum.ORDINARY.value());
userInfoInToken.setEnabled(user.getStatus() == 1);
userInfoInToken.setOpenid(wxInfo.getOpenid());
// 存储token返回vo
tokenInfoVO = tokenStore.storeAndGetVo(userInfoInToken);
} catch (Exception e) {
result = "";
e.printStackTrace();
}
return ServerResponseEntity.success(tokenInfoVO);
}
/**
* 解密数据
*
* @return
* @throws Exception
*/
/**
* 解密数据
*
* @return
* @throws Exception
*/
@PostMapping("/wxMerchantLogin")
@Transactional
public ServerResponseEntity<TokenInfoVO> wxMerchantLogin(@RequestBody Phone phone) {
String result = "";
Environment environment = applicationContext.getEnvironment();
String appId = environment.getProperty("weixin.merchantappid");
String secret = environment.getProperty("weixin.merchantsecret");
TokenInfoVO tokenInfoVO = new TokenInfoVO();
//User user = userService.selectUserByOpenId(phone.getOpenid());
String accessToken = weiXinUtil.postToken(appId, secret);
WxInfo wxInfo = getWeChatMerchantOpenId(phone.getOpenIdCode());
String tel = weiXinService.getUserPhoneNumber(accessToken, phone.getPhoneCode());
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(User::getUserMobile, tel);
queryWrapper.eq(User::getUserType, 2);
User mUser = userService.getOne(queryWrapper);
if (mUser == null) {
return ServerResponseEntity.showFailMsg("您未注册商户");
/* User user = new User();
String userId = IdUtil.simpleUUID();
Date now = new Date();
user.setModifyTime(now);
user.setUserRegtime(now);
user.setUserMobile(tel);
user.setUserType(2);
user.setOpenid(wxInfo.getOpenid());
user.setUnionid(wxInfo.getUnionid());
user.setUserId(userId);
user.setStatus(1);
userService.save(user);*/
}else {
mUser.setOpenid(wxInfo.getOpenid());
mUser.setUnionid(wxInfo.getUnionid());
userService.updateById(mUser);
}
try {
UserInfoInTokenBO userInfoInToken = new UserInfoInTokenBO();
userInfoInToken.setUserId(mUser.getUserId());
userInfoInToken.setSysType(SysTypeEnum.ORDINARY.value());
userInfoInToken.setEnabled(mUser.getStatus() == 1);
userInfoInToken.setOpenid(wxInfo.getOpenid());
// 存储token返回vo
tokenInfoVO = tokenStore.storeAndGetVo(userInfoInToken);
} catch (Exception e) {
result = "";
e.printStackTrace();
}
return ServerResponseEntity.success(tokenInfoVO);
}
/* @PostMapping("/wxMerchantLogin")
@Transactional
public ServerResponseEntity<TokenInfoVO> wxMerchantLogin(@RequestBody Phone phone) {
String result = "";
Environment environment = applicationContext.getEnvironment();
String appId = environment.getProperty("weixin.merchantappid");
String secret = environment.getProperty("weixin.merchantsecret");
TokenInfoVO tokenInfoVO = new TokenInfoVO();
//User user = userService.selectUserByOpenId(phone.getOpenid());
String accessToken = weiXinUtil.postToken(appId, secret);
WxInfo wxInfo = getWeChatOpenId(phone.getOpenIdCode());
String tel = weiXinService.getUserPhoneNumber(accessToken, phone.getPhoneCode());
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(User::getUserMobile, tel);
queryWrapper.eq(User::getUserType, 2);
User mUser = userService.getOne(queryWrapper);
if (mUser == null) {
return ServerResponseEntity.showFailMsg("您未注册商户");
}
mUser.setOpenid(wxInfo.getOpenid());
mUser.setUnionid(wxInfo.getUnionid());
userService.updateById(mUser);
try {
UserInfoInTokenBO userInfoInToken = new UserInfoInTokenBO();
userInfoInToken.setUserId(mUser.getUserId());
userInfoInToken.setSysType(SysTypeEnum.ORDINARY.value());
userInfoInToken.setEnabled(mUser.getStatus() == 1);
userInfoInToken.setOpenid(wxInfo.getOpenid());
// 存储token返回vo
tokenInfoVO = tokenStore.storeAndGetVo(userInfoInToken);
} catch (Exception e) {
result = "";
e.printStackTrace();
}
return ServerResponseEntity.success(tokenInfoVO);
}*/
public static WxInfo getWeChatOpenId(String code) {
Environment environment = applicationContext.getEnvironment();
// String url = "https://api.weixin.qq.com/sns/jscode2session?appid=wx2021d20e26e151ad&secret=131ba5acfd02fbd4cf93779a11b5579e&js_code=" + code + "&grant_type=authorization_code";
String url = "https://api.weixin.qq.com/sns/jscode2session?appid=" + environment.getProperty("weixin.appid") + "&secret=" + environment.getProperty("weixin.secret") + "&js_code=" + code + "&grant_type=authorization_code";
try {
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url(url).build();
Response response = client.newCall(request).execute();
String responseData = response.body().string();
System.out.println("responseData+" + responseData);
/*if (responseData.contains("openid")) {
String openid = responseData.substring(responseData.indexOf("openid") + 7, responseData.length() - 1);
return openid;
} else {
return "遇见某种问题或空值";
}*/
WxInfo wxInfo = JSON.parseObject(responseData, WxInfo.class);
return wxInfo;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public static WxInfo getWeChatMerchantOpenId(String code) {
Environment environment = applicationContext.getEnvironment();
// String url = "https://api.weixin.qq.com/sns/jscode2session?appid=wx2021d20e26e151ad&secret=131ba5acfd02fbd4cf93779a11b5579e&js_code=" + code + "&grant_type=authorization_code";
String url = "https://api.weixin.qq.com/sns/jscode2session?appid=" + environment.getProperty("weixin.merchantappid") + "&secret=" + environment.getProperty("weixin.merchantsecret") + "&js_code=" + code + "&grant_type=authorization_code";
try {
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url(url).build();
Response response = client.newCall(request).execute();
String responseData = response.body().string();
System.out.println("responseData+" + responseData);
/*if (responseData.contains("openid")) {
String openid = responseData.substring(responseData.indexOf("openid") + 7, responseData.length() - 1);
return openid;
} else {
return "遇见某种问题或空值";
}*/
WxInfo wxInfo = JSON.parseObject(responseData, WxInfo.class);
return wxInfo;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public static String getStringRandom(int length) {
String val = "";
Random random = new Random();
//参数length表明生成几位随机数
for (int i = 0; i < length; i++) {
String charOrNum = random.nextInt(2) % 2 == 0 ? "char" : "num";
//输出字母仍是数字
if ("char".equalsIgnoreCase(charOrNum)) {
//输出是大写字母仍是小写字母
int temp = random.nextInt(2) % 2 == 0 ? 65 : 97;
val += (char) (random.nextInt(26) + temp);
} else if ("num".equalsIgnoreCase(charOrNum)) {
val += String.valueOf(random.nextInt(10));
}
}
return "wx" + val;
}
}

View File

@@ -0,0 +1,50 @@
package com.pxdj.security.api.wx;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
/**
* @author wut
* @PackageName:com.szbt.framework.security.wx
* @ClassName:WxCodeAuthenticationProvider
* @Description: wx登陆鉴权 Provider要求实现 AuthenticationProvider 接口
* @date 2023/6/12 10:47
*/
public class WxCodeAuthenticationProvider implements AuthenticationProvider {
private UserDetailsService userDetailsService;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
WxCodeAuthenticationToken authenticationToken = (WxCodeAuthenticationToken) authentication;
String openId = (String) authenticationToken.getPrincipal();
UserDetails userDetails = userDetailsService.loadUserByUsername(openId);
// 此时鉴权成功后,应当重新 new 一个拥有鉴权的 authenticationResult 返回
WxCodeAuthenticationToken authenticationResult = new WxCodeAuthenticationToken(userDetails, userDetails.getAuthorities());
authenticationResult.setDetails(authenticationToken.getDetails());
return authenticationResult;
}
@Override
public boolean supports(Class<?> authentication) {
// 判断 authentication 是不是 WxCodeAuthenticationToken 的子类或子接口
return WxCodeAuthenticationToken.class.isAssignableFrom(authentication);
}
public UserDetailsService getUserDetailsService() {
return userDetailsService;
}
public void setUserDetailsService(UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
}

View File

@@ -0,0 +1,69 @@
package com.pxdj.security.api.wx;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.SpringSecurityCoreVersion;
import java.util.Collection;
/**
* @author wut
* @PackageName:com.szbt.framework.security.authentication
* @ClassName:WxCodeAuthenticationToken
* @Description: 自定义微信登录token验证
* @date 2023/6/12 10:40
*/
public class WxCodeAuthenticationToken extends AbstractAuthenticationToken {
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
/**
* 在 UsernamePasswordAuthenticationToken 中该字段代表登录的用户名,
* 在这里就代表登录的openId
*/ private final Object principal;
/**
* 构建一个没有鉴权的 WxCodeAuthenticationToken
*/ public WxCodeAuthenticationToken(Object principal) {
super(null);
this.principal = principal;
setAuthenticated(false);
}
/**
* 构建拥有鉴权的 WxCodeAuthenticationToken
*/
public WxCodeAuthenticationToken(Object principal, Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
// must use super, as we override
super.setAuthenticated(true);
}
@Override
public Object getCredentials() {
return null;
}
@Override
public Object getPrincipal() {
return this.principal;
}
@Override
public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
if (isAuthenticated) {
throw new IllegalArgumentException(
"Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
}
super.setAuthenticated(false);
}
@Override
public void eraseCredentials() {
super.eraseCredentials();
}
}

View File

@@ -0,0 +1,10 @@
package com.pxdj.security.api.wx;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/foods/food")
public class WxIdController {
}

View File

@@ -0,0 +1,64 @@
package com.pxdj.security.api.wx;
import java.nio.charset.Charset;
import java.util.Arrays;
/**
* 微信小程序加解密
* @author liuyazhuang
*
*/
public class WxPKCS7Encoder {
private static final Charset CHARSET = Charset.forName("utf-8");
private static final int BLOCK_SIZE = 32;
/**
* 获得对明文进行补位填充的字节.
*
* @param count
* 需要进行填充补位操作的明文字节个数
* @return 补齐用的字节数组
*/
public static byte[] encode(int count) {
// 计算需要填充的位数
int amountToPad = BLOCK_SIZE - (count % BLOCK_SIZE);
if (amountToPad == 0) {
amountToPad = BLOCK_SIZE;
}
// 获得补位所用的字符
char padChr = chr(amountToPad);
String tmp = new String();
for (int index = 0; index < amountToPad; index++) {
tmp += padChr;
}
return tmp.getBytes(CHARSET);
}
/**
* 删除解密后明文的补位字符
*
* @param decrypted
* 解密后的明文
* @return 删除补位字符后的明文
*/
public static byte[] decode(byte[] decrypted) {
int pad = decrypted[decrypted.length - 1];
if (pad < 1 || pad > 32) {
pad = 0;
}
return Arrays.copyOfRange(decrypted, 0, decrypted.length - pad);
}
/**
* 将数字转化成ASCII码对应的字符用于对明文进行补码
*
* @param a
* 需要转化的数字
* @return 转化得到的字符
*/
public static char chr(int a) {
byte target = (byte) (a & 0xFF);
return (char) target;
}
}

View File

@@ -0,0 +1,81 @@
package com.pxdj.security.api.wx;
import com.alibaba.fastjson.JSON;
import com.pxdj.api.domain.User;
import com.pxdj.security.api.model.WxInfo;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.springframework.core.env.Environment;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import static com.pxdj.common.utils.SpringContextUtils.applicationContext;
@RestController
@RequestMapping("/wx")
public class wx {
@PostMapping("/getWeChatOpenId")
public static String getWeChatOpenId( @RequestBody String code) {
String[] split = code.split(":");
String s = split[1].toString();
String substring = s.substring(1, s.length() - 2);
code=substring;
Environment environment = applicationContext.getEnvironment();
// String url = "https://api.weixin.qq.com/sns/jscode2session?appid=wx2021d20e26e151ad&secret=131ba5acfd02fbd4cf93779a11b5579e&js_code=" + code + "&grant_type=authorization_code";
String url = "https://api.weixin.qq.com/sns/jscode2session?appid="+environment.getProperty("weixin.appid")+"&secret="+environment.getProperty("weixin.secret")+"&js_code=" + code + "&grant_type=authorization_code";
try {
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url(url).build();
Response response = client.newCall(request).execute();
String responseData = response.body().string();
System.out.println("responseData+"+responseData);
/*if (responseData.contains("openid")) {
String openid = responseData.substring(responseData.indexOf("openid") + 7, responseData.length() - 1);
return openid;
} else {
return "遇见某种问题或空值";
}*/
WxInfo wxInfo = JSON.parseObject(responseData, WxInfo.class);
return JSON.toJSONString(wxInfo);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
@PostMapping("/getWeChatMerchantOpenId")
public static String getWeChatMerchantOpenId( @RequestBody String code) {
String[] split = code.split(":");
String s = split[1].toString();
String substring = s.substring(1, s.length() - 2);
code=substring;
Environment environment = applicationContext.getEnvironment();
// String url = "https://api.weixin.qq.com/sns/jscode2session?appid=wx2021d20e26e151ad&secret=131ba5acfd02fbd4cf93779a11b5579e&js_code=" + code + "&grant_type=authorization_code";
String url = "https://api.weixin.qq.com/sns/jscode2session?appid="+environment.getProperty("weixin.merchantappid")+"&secret="+environment.getProperty("weixin.merchantsecret")+"&js_code=" + code + "&grant_type=authorization_code";
//wxMaService.getUserService().getSessionInfo(loginParam.getCode());
try {
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url(url).build();
Response response = client.newCall(request).execute();
String responseData = response.body().string();
System.out.println("responseData+"+responseData);
/*if (responseData.contains("openid")) {
String openid = responseData.substring(responseData.indexOf("openid") + 7, responseData.length() - 1);
return openid;
} else {
return "遇见某种问题或空值";
}*/
WxInfo wxInfo = JSON.parseObject(responseData, WxInfo.class);
return JSON.toJSONString(wxInfo);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}