Commit 3908cb9f authored by hongguangwu's avatar hongguangwu

工资条等。薪资、upms、auth服务要重启

parent 1c030b6c
...@@ -80,7 +80,7 @@ public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter { ...@@ -80,7 +80,7 @@ public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
.failureHandler(authenticationFailureHandler()).and().logout() .failureHandler(authenticationFailureHandler()).and().logout()
.logoutSuccessHandler(logoutSuccessHandler()).deleteCookies("JSESSIONID").invalidateHttpSession(true) .logoutSuccessHandler(logoutSuccessHandler()).deleteCookies("JSESSIONID").invalidateHttpSession(true)
.and() .and()
.authorizeRequests().antMatchers("/**/login","/token/**", "/actuator/**", "/mobile/**", "/oauth/token").permitAll() .authorizeRequests().antMatchers("/**/login","/token/**", "/actuator/**", "/mobile/**", "/oauth/token","/weixin/callback").permitAll()
.anyRequest().authenticated().and().csrf().disable(); .anyRequest().authenticated().and().csrf().disable();
} }
......
...@@ -18,34 +18,54 @@ package com.yifu.cloud.plus.v1.yifu.auth.endpoint; ...@@ -18,34 +18,54 @@ package com.yifu.cloud.plus.v1.yifu.auth.endpoint;
import cn.hutool.core.map.MapUtil; import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.yifu.cloud.plus.v1.yifu.admin.api.dto.UserInfo;
import com.yifu.cloud.plus.v1.yifu.admin.api.entity.SysUser;
import com.yifu.cloud.plus.v1.yifu.auth.util.LoginTool;
import com.yifu.cloud.plus.v1.yifu.auth.util.weixin.callback.WeixinLoginCallback;
import com.yifu.cloud.plus.v1.yifu.auth.util.weixin.model.WeixinError;
import com.yifu.cloud.plus.v1.yifu.auth.util.weixin.model.WeixinUserInfo;
import com.yifu.cloud.plus.v1.yifu.auth.vo.WxUserVo;
import com.yifu.cloud.plus.v1.yifu.common.core.constant.CacheConstants; import com.yifu.cloud.plus.v1.yifu.common.core.constant.CacheConstants;
import com.yifu.cloud.plus.v1.yifu.common.core.constant.CommonConstants; import com.yifu.cloud.plus.v1.yifu.common.core.constant.CommonConstants;
import com.yifu.cloud.plus.v1.yifu.common.core.constant.SecurityConstants;
import com.yifu.cloud.plus.v1.yifu.common.core.util.Common;
import com.yifu.cloud.plus.v1.yifu.common.core.util.R; import com.yifu.cloud.plus.v1.yifu.common.core.util.R;
import com.yifu.cloud.plus.v1.yifu.common.core.util.SpringContextHolder; import com.yifu.cloud.plus.v1.yifu.common.core.util.SpringContextHolder;
import com.yifu.cloud.plus.v1.yifu.common.core.vo.YifuUser;
import com.yifu.cloud.plus.v1.yifu.common.dapr.config.DaprUpmsProperties;
import com.yifu.cloud.plus.v1.yifu.common.dapr.util.HttpDaprUtil;
import com.yifu.cloud.plus.v1.yifu.common.security.annotation.Inner; import com.yifu.cloud.plus.v1.yifu.common.security.annotation.Inner;
import com.yifu.cloud.plus.v1.yifu.common.security.util.AuthUtils;
import com.yifu.cloud.plus.v1.yifu.common.security.util.SecurityUtils; import com.yifu.cloud.plus.v1.yifu.common.security.util.SecurityUtils;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.CacheManager; import org.springframework.cache.CacheManager;
import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.event.LogoutSuccessEvent; import org.springframework.security.authentication.event.LogoutSuccessEvent;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.common.OAuth2AccessToken; import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2RefreshToken; import org.springframework.security.oauth2.common.OAuth2RefreshToken;
import org.springframework.security.oauth2.provider.AuthorizationRequest; import org.springframework.security.oauth2.common.exceptions.InvalidClientException;
import org.springframework.security.oauth2.provider.ClientDetails; import org.springframework.security.oauth2.provider.*;
import org.springframework.security.oauth2.provider.ClientDetailsService; import org.springframework.security.oauth2.provider.request.DefaultOAuth2RequestValidator;
import org.springframework.security.oauth2.provider.OAuth2Authentication; import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore; import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession; import javax.servlet.http.HttpSession;
import java.util.List; import java.util.*;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
/** /**
...@@ -56,8 +76,12 @@ import java.util.stream.Collectors; ...@@ -56,8 +76,12 @@ import java.util.stream.Collectors;
@RestController @RestController
@RequiredArgsConstructor @RequiredArgsConstructor
@RequestMapping("/token") @RequestMapping("/token")
@EnableConfigurationProperties(DaprUpmsProperties.class)
@Tag(name = "获取Token")
public class YifuTokenEndpoint { public class YifuTokenEndpoint {
private final DaprUpmsProperties daprUpmsProperties;
private final ClientDetailsService clientDetailsService; private final ClientDetailsService clientDetailsService;
private final TokenStore tokenStore; private final TokenStore tokenStore;
...@@ -66,6 +90,36 @@ public class YifuTokenEndpoint { ...@@ -66,6 +90,36 @@ public class YifuTokenEndpoint {
private final CacheManager cacheManager; private final CacheManager cacheManager;
private final PasswordEncoder passwordEncoder;
private final AuthorizationServerTokenServices defaultAuthorizationServerTokenServices;
/**
* 构建userdetails
*
* @param result 用户信息
* @return
*/
private UserDetails getUserDetails(R<UserInfo> result) {
if (result == null || result.getData() == null) {
throw new UsernameNotFoundException("用户不存在");
}
UserInfo info = result.getData();
// 获取资源
Set<String> dbAuthsSet = new HashSet<>(Arrays.asList(info.getPermissions()));
Collection<? extends GrantedAuthority> authorities
= AuthorityUtils.createAuthorityList(dbAuthsSet.toArray(new String[0]));
SysUser user = info.getSysUser();
// 构造security用户
YifuUser thisUser = new YifuUser(user.getUserId(), user.getDeptId(),user.getDeptName(), user.getUsername(),
user.getNickname(),user.getSystemFlag(), SecurityConstants.BCRYPT + user.getPassword(),
user.getPhone(), true, true, true,
CommonConstants.STATUS_NORMAL.equals(user.getLockFlag()),
user.getUserGroup(),authorities, user.getLdapDn());
thisUser.setClientRoleMap(info.getClientRoleMap());
return thisUser;
}
/** /**
* 认证页面 * 认证页面
* @param modelAndView * @param modelAndView
...@@ -165,4 +219,90 @@ public class YifuTokenEndpoint { ...@@ -165,4 +219,90 @@ public class YifuTokenEndpoint {
return R.ok(result); return R.ok(result);
} }
/**
* 微信扫码回调登录业务处理
* @param code
* @param state 客户端加密串
* @param type 0 或空 取值配置 1 取值传参
* @return
*/
@GetMapping(value = "/weixin/callback")
@ResponseBody
public String wxCallback(HttpServletRequest request,@RequestParam String code, String state, String type, String appid , String secret) {
return LoginTool.loginTwo(code, appid, secret, getCallback(request, state));
}
private WeixinLoginCallback getCallback(HttpServletRequest request, String state) {
return new WeixinLoginCallback() {
@Override
public String handle(WeixinUserInfo userInfo) {
if (null != userInfo && null != userInfo.getOpenid()) {
SysUser user = new SysUser();
user.setWxOpenid(userInfo.getOpenid());
R<UserInfo> result = HttpDaprUtil.invokeMethodPost(daprUpmsProperties.getAppUrl(), daprUpmsProperties.getAppId()
, "/user/inner/getSysUserByWxOpenId", user, UserInfo.class, SecurityConstants.FROM_IN);
OAuth2AccessToken oAuth2AccessToken = null;
if (null != result && null != result.getData() && result.getData().getSysUser() != null) {
SysUser sysUser = result.getData().getSysUser();
if (!CommonConstants.ZERO_STRING.equals(sysUser.getLockFlag())) {
return JSON.toJSONString(R.failed("用户".concat(sysUser.getNickname()).concat("账号:").concat(sysUser.getUsername()).concat("已被锁定,请联系管理员。")));
}
UserDetails userDetails = getUserDetails(result);
try {
String header = request.getHeader(com.yifu.cloud.plus.v1.yifu.auth.constants.SecurityConstants.CLIENT_HEADER_KEY_NEW);
if(Common.isEmpty(header)){
header = request.getHeader(com.yifu.cloud.plus.v1.yifu.auth.constants.SecurityConstants.CLIENT_HEADER_KEY);
//兼容老逻辑
if(Common.isEmpty(header)){
header = request.getHeader(HttpHeaders.AUTHORIZATION);
}
}
if (Common.isEmpty(header)) {
throw new InvalidClientException("请求头中client信息为空");
}
String[] tokens = AuthUtils.extractAndDecodeHeader(header);
assert tokens.length == 2;
String clientId = tokens[0];
ClientDetails clientDetails = clientDetailsService.loadClientByClientId(clientId);
//校验secret
if (!passwordEncoder.matches(tokens[1], clientDetails.getClientSecret())) {
throw new InvalidClientException("Given client ID does not match authenticated client");
}
TokenRequest tokenRequest = new TokenRequest(MapUtil.newHashMap(), clientId, clientDetails.getScope(), "mobile");
//校验scope
new DefaultOAuth2RequestValidator().validateScope(tokenRequest, clientDetails);
OAuth2Request oAuth2Request = tokenRequest.createOAuth2Request(clientDetails);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken
(userDetails, userDetails.getPassword());
OAuth2Authentication oAuth2Authentication = new OAuth2Authentication(oAuth2Request, authentication);
oAuth2AccessToken = defaultAuthorizationServerTokenServices.createAccessToken(oAuth2Authentication);
} catch (Exception e) {
log.info("客户端解析出错。");
return JSON.toJSONString(R.failed("无此客户端"));
}
WxUserVo wxUserVo = new WxUserVo();
wxUserVo.setOauth2AccessToken(oAuth2AccessToken);
wxUserVo.setWeixinUserInfo(userInfo);
wxUserVo.setUser(sysUser);
return JSON.toJSONString(R.ok(wxUserVo));
} else {
return JSON.toJSONString(R.other(userInfo, "该用户未注册", CommonConstants.REGISTERED));
}
}
//查询用户
log.info("登录成功业务处理");
return JSON.toJSONString(R.failed("openid为空"));
}
@Override
public String errer(WeixinError error) {
log.info("登录失败业务处理");
log.error(error.getErrmsg());
return JSON.toJSONString(R.failed(error.getErrmsg()));
}
};
}
} }
package com.yifu.cloud.plus.v1.yifu.auth;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AuthBeanAutoConfiguration {
}
package com.yifu.cloud.plus.v1.yifu.auth.util;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.client.RestTemplate;
/**
* 三方登录服务接入
* @author daijie_jay
* @since 2017年11月29日
*/
public abstract class AbstractLoginService<T extends LoginProperties> implements LoginService {
protected static final String REDIRECT = "redirect:";
@Autowired
protected RestTemplate restTemplate;
@Autowired
protected T properties;
public String getRedirectUrl() {
return REDIRECT + properties.getRedirectUri();
}
public String getErrorUrl() {
return REDIRECT + properties.getErrorUri();
}
@SuppressWarnings("unchecked")
@Override
public T getProperties(){
return properties;
}
@Override
public String loadAuthPage() {
return this.loadAuthPage(null);
}
}
package com.yifu.cloud.plus.v1.yifu.auth.util;
public interface LoginCallback<T, E> {
/**
* 登录成功后逻辑处理,处理结束如果有则返回错误信息
* @param userInfo 第三方用户实体
*/
public String handle(T userInfo);
/**
* 登录失败后逻辑处理 ,处理结束如果有则返回错误信息
* @param error 第三方错误实体
*/
public String errer(E error);
}
package com.yifu.cloud.plus.v1.yifu.auth.util;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
* 登录属性配置初始化
*/
// @Component
public class LoginProperties {
/*public String appid = "wxcb81e4a2ad8d9012";
public String appsecret = "546f366801ad42fec8e67393ed484879";*/
public String appid = "wxfb3a14ee64c87046";
public String appsecret = "e2143c5a1f635e039122de27f29354fe";
public String redirectUri = "https://www.ngrok.xiaomiqiu.cn/admin/hi";
public String errorUri = "/error";
public String callbackUri = "https://www.ngrok.xiaomiqiu.cn/oauth/weixin/callback";
/*@Value("${weixin.login.appid}")
protected String appid;
@Value("${weixin.login.appsecret}")
protected String appsecret;
@Value("${weixin.login.redirectUri}")
protected String redirectUri;
@Value("${weixin.login.errorUri}")
protected String errorUri;
@Value("${weixin.login.callbackUri}")
protected String callbackUri;*/
public String getAppid() {
return appid;
}
public void setAppid(String appid) {
this.appid = appid;
}
public String getAppsecret() {
return appsecret;
}
public void setAppsecret(String appsecret) {
this.appsecret = appsecret;
}
public String getRedirectUri() {
return redirectUri;
}
public void setRedirectUri(String redirectUri) {
this.redirectUri = redirectUri;
}
public String getErrorUri() {
return errorUri;
}
public void setErrorUri(String errorUri) {
this.errorUri = errorUri;
}
public String getCallbackUri() {
return callbackUri;
}
public void setCallbackUri(String callbackUri) {
this.callbackUri = callbackUri;
}
}
package com.yifu.cloud.plus.v1.yifu.auth.util;
/**
* 返回结果
*/
public interface LoginResult {
/**
* 是否正常返回
* @return Boolean
*/
Boolean getResult();
}
package com.yifu.cloud.plus.v1.yifu.auth.util;
/**
* 三方登录服务接入
*/
public interface LoginService {
public <T extends LoginProperties> T getProperties();
/**
* 加载第三方授权页
* @return String
*/
String loadAuthPage();
/**
* 加载第三方授权页
* @param state 数据声明
* @return String
*/
String loadAuthPage(String state);
}
package com.yifu.cloud.plus.v1.yifu.auth.util;
import com.yifu.cloud.plus.v1.yifu.auth.util.weixin.WeixinLoginTool;
import com.yifu.cloud.plus.v1.yifu.auth.util.weixin.callback.WeixinLoginCallback;
@SuppressWarnings("unchecked")
public class LoginTool {
/**
* 支持前端传惨 appid secret 兼容 多个公众号的情况
* @param appAuthCode
* @param appid
* @param secret
* @param callback
* @return
*/
public static String loginTwo(String appAuthCode, String appid ,String secret, LoginCallback callback){
return WeixinLoginTool.login(appAuthCode,appid,secret, (WeixinLoginCallback) callback);
}
}
package com.yifu.cloud.plus.v1.yifu.auth.util.annotation;
import com.yifu.cloud.plus.v1.yifu.auth.AuthBeanAutoConfiguration;
import com.yifu.cloud.plus.v1.yifu.auth.util.config.WeixinLoginBeanAutoConfiguration;
import com.yifu.cloud.plus.v1.yifu.auth.util.weixin.WeixinLoginTool;
import org.springframework.context.annotation.Import;
import java.lang.annotation.*;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({AuthBeanAutoConfiguration.class, WeixinLoginBeanAutoConfiguration.class, WeixinLoginTool.class})
public @interface EnableWeixinLogin {
}
package com.yifu.cloud.plus.v1.yifu.auth.util.config;
import com.yifu.cloud.plus.v1.yifu.auth.util.weixin.WeixinLoignProperties;
import com.yifu.cloud.plus.v1.yifu.auth.util.weixin.callback.WeixinLoginCallbackController;
import com.yifu.cloud.plus.v1.yifu.auth.util.weixin.service.WeixinLoginService;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 微信登录自动装置
*/
//@Configuration
// @EnableConfigurationProperties({WeixinLoignProperties.class})
public class WeixinLoginBeanAutoConfiguration {
@Bean("weixinLoginService")
public WeixinLoginService loginService() {
return new WeixinLoginService();
}
@Bean
public WeixinLoginCallbackController loginCallbackController() {
return new WeixinLoginCallbackController();
}
}
package com.yifu.cloud.plus.v1.yifu.auth.util.weixin;
/**
* 微信登录常量配置
*/
public class WeixinLoginConstants {
public static final String HOST_API = "https://api.weixin.qq.com";
/**
* 临时code获取accessToken
*/
public static final String ACCESS_TOKEN = "/sns/oauth2/access_token";
/**
* 获取用户信息
*/
public static final String USER_INFO = "/sns/userinfo";
}
package com.yifu.cloud.plus.v1.yifu.auth.util.weixin;
import com.yifu.cloud.plus.v1.yifu.auth.util.LoginResult;
import com.yifu.cloud.plus.v1.yifu.auth.util.weixin.callback.WeixinLoginCallback;
import com.yifu.cloud.plus.v1.yifu.auth.util.weixin.model.WeixinAccessToken;
import com.yifu.cloud.plus.v1.yifu.auth.util.weixin.model.WeixinError;
import com.yifu.cloud.plus.v1.yifu.auth.util.weixin.model.WeixinUserInfo;
import com.yifu.cloud.plus.v1.yifu.auth.util.weixin.service.WeixinLoginService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* 微信登录工具
*/
@Component
public class WeixinLoginTool {
private static WeixinLoginService wxLoginService;
@Autowired
public void setWeixinLoginService(WeixinLoginService wxLoginService) {
WeixinLoginTool.wxLoginService = wxLoginService;
}
/**
* 微信登录
* 实用于已得到微信临时code
* @param appAuthCode 临时code
* @param callback 回调函数
* @return String
*/
public static String login(String appAuthCode, WeixinLoginCallback callback){
LoginResult result = wxLoginService.getAccessToken(appAuthCode);
if(result.getResult()){
WeixinAccessToken accessToken = (WeixinAccessToken) result;
result = wxLoginService.getWxUserInfo(accessToken);
if(result.getResult()){
WeixinUserInfo userInfo = (WeixinUserInfo) result;
return callback.handle(userInfo);
}
}
return callback.errer((WeixinError) result);
}
/**
* 微信登录
* 实用于已得到微信临时code
* @param appAuthCode 临时code
* @param callback 回调函数
* @return String
*/
public static String login(String appAuthCode, String appid ,String secret, WeixinLoginCallback callback){
LoginResult result = wxLoginService.getAccessToken(appAuthCode,appid,secret);
if(result.getResult()){
WeixinAccessToken accessToken = (WeixinAccessToken) result;
result = wxLoginService.getWxUserInfo(accessToken);
if(result.getResult()){
WeixinUserInfo userInfo = (WeixinUserInfo) result;
return callback.handle(userInfo);
}
}
return callback.errer((WeixinError) result);
}
}
package com.yifu.cloud.plus.v1.yifu.auth.util.weixin;
import com.yifu.cloud.plus.v1.yifu.auth.util.LoginProperties;
import org.springframework.stereotype.Component;
/**
* 微信登录属性配置初始化
*/
@Component
public class WeixinLoignProperties extends LoginProperties {
private String token;
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
}
package com.yifu.cloud.plus.v1.yifu.auth.util.weixin.callback;
import com.yifu.cloud.plus.v1.yifu.auth.util.LoginCallback;
import com.yifu.cloud.plus.v1.yifu.auth.util.weixin.model.WeixinError;
import com.yifu.cloud.plus.v1.yifu.auth.util.weixin.model.WeixinUserInfo;
/**
* 微信登录回调
*/
public interface WeixinLoginCallback extends LoginCallback<WeixinUserInfo, WeixinError> {
}
package com.yifu.cloud.plus.v1.yifu.auth.util.weixin.callback;
import com.yifu.cloud.plus.v1.yifu.auth.util.weixin.WeixinLoginTool;
import org.springframework.web.bind.annotation.RequestParam;
/**
* 微信登录扫描回调
*/
/*@Controller*/
public class WeixinLoginCallbackController {
/**
* 处理回调业务
* @param code 临时code
* @param state 数据声明
* @param callback 回调函数
* @return String
*/
/*@GetMapping(value = WeixinLoginConstants.LOGIN_CALLBACK)*/
public String callback(@RequestParam String code, String state, WeixinLoginCallback callback){
return WeixinLoginTool.login(code, callback);
}
}
package com.yifu.cloud.plus.v1.yifu.auth.util.weixin.model;
import com.yifu.cloud.plus.v1.yifu.auth.util.LoginResult;
/**
* 微信认证权限的实体
*/
public class WeixinAccessToken implements LoginResult {
//接口调用凭证
private String access_token;
//access_token接口调用凭证超时时间,单位(秒)
private Long expires_in;
//用户刷新access_token
private String refresh_token;
//授权用户唯一标识
private String openid;
//用户授权的作用域,使用逗号(,)分隔
private String scope;
public String getAccess_token() {
return access_token;
}
public void setAccess_token(String access_token) {
this.access_token = access_token;
}
public Long getExpires_in() {
return expires_in;
}
public void setExpires_in(Long expires_in) {
this.expires_in = expires_in;
}
public String getRefresh_token() {
return refresh_token;
}
public void setRefresh_token(String refresh_token) {
this.refresh_token = refresh_token;
}
public String getOpenid() {
return openid;
}
public void setOpenid(String openid) {
this.openid = openid;
}
public String getScope() {
return scope;
}
public void setScope(String scope) {
this.scope = scope;
}
@Override
public Boolean getResult() {
return true;
}
}
package com.yifu.cloud.plus.v1.yifu.auth.util.weixin.model;
import com.yifu.cloud.plus.v1.yifu.auth.util.LoginResult;
/**
* 错误信息
*/
public class WeixinError implements LoginResult {
private String errcode;
private String errmsg;
public String getErrcode() {
return errcode;
}
public void setErrcode(String errcode) {
this.errcode = errcode;
}
public String getErrmsg() {
return errmsg;
}
public void setErrmsg(String errmsg) {
this.errmsg = errmsg;
}
@Override
public Boolean getResult() {
return false;
}
}
package com.yifu.cloud.plus.v1.yifu.auth.util.weixin.model;
import com.yifu.cloud.plus.v1.yifu.auth.util.LoginResult;
import java.util.List;
/**
* 用户信息
*/
public class WeixinUserInfo implements LoginResult {
//普通用户的标识,对当前开发者帐号唯一
private String openid;
//普通用户昵称
private String nickname;
//普通用户性别,1为男性,2为女性
private Integer sex;
//普通用户个人资料填写的省份
private String province;
//普通用户个人资料填写的城市
private String city;
//国家,如中国为CN
private String country;
//用户头像,最后一个数值代表正方形头像大小(有0、46、64、96、132数值可选,0代表640*640正方形头像),用户没有头像时该项为空
private String headimgurl;
//用户特权信息,json数组,如微信沃卡用户为(chinaunicom)
private List<String> privilege;
//用户统一标识。针对一个微信开放平台帐号下的应用,同一用户的unionid是唯一的。
private String unionid;
public String getOpenid() {
return openid;
}
public void setOpenid(String openid) {
this.openid = openid;
}
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
public Integer getSex() {
return sex;
}
public void setSex(Integer sex) {
this.sex = sex;
}
public String getProvince() {
return province;
}
public void setProvince(String province) {
this.province = province;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
public String getHeadimgurl() {
return headimgurl;
}
public void setHeadimgurl(String headimgurl) {
this.headimgurl = headimgurl;
}
public List<String> getPrivilege() {
return privilege;
}
public void setPrivilege(List<String> privilege) {
this.privilege = privilege;
}
public String getUnionid() {
return unionid;
}
public void setUnionid(String unionid) {
this.unionid = unionid;
}
@Override
public Boolean getResult() {
return true;
}
}
package com.yifu.cloud.plus.v1.yifu.auth.util.weixin.service;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.yifu.cloud.plus.v1.yifu.auth.util.AbstractLoginService;
import com.yifu.cloud.plus.v1.yifu.auth.util.LoginResult;
import com.yifu.cloud.plus.v1.yifu.auth.util.weixin.WeixinLoginConstants;
import com.yifu.cloud.plus.v1.yifu.auth.util.weixin.WeixinLoignProperties;
import com.yifu.cloud.plus.v1.yifu.auth.util.weixin.model.WeixinAccessToken;
import com.yifu.cloud.plus.v1.yifu.auth.util.weixin.model.WeixinError;
import com.yifu.cloud.plus.v1.yifu.auth.util.weixin.model.WeixinUserInfo;
import com.yifu.cloud.plus.v1.yifu.common.core.util.Common;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
/**
* 微信登录服务
*/
@Service
public class WeixinLoginService extends AbstractLoginService<WeixinLoignProperties> {
private static final Logger logger = LoggerFactory.getLogger(WeixinLoginService.class);
public LoginResult getAccessToken(String code) {
StringBuilder uri = new StringBuilder();
uri.append(WeixinLoginConstants.HOST_API + WeixinLoginConstants.ACCESS_TOKEN + "?appid=");
uri.append(properties.getAppid());
uri.append("&secret=").append(properties.getAppsecret());
uri.append("&code=" + code).append("&grant_type=authorization_code");
try {
String url = uri.toString();
String result = restTemplate.getForObject(url, String.class);
JSONObject json = JSONUtil.parseObj(result);
if(json.getStr("access_token") != null){
WeixinAccessToken accessToken = new WeixinAccessToken();
accessToken.setAccess_token(json.getStr("access_token"));
accessToken.setOpenid(json.getStr("open_id"));
accessToken.setExpires_in(json.getLong("expires_in"));
accessToken.setRefresh_token(json.getStr("refresh_token"));
accessToken.setScope(json.getStr("scope"));
return accessToken;
}else{
WeixinError error = new WeixinError();
error.setErrcode(json.getStr("errcode"));
error.setErrmsg(json.getStr("errmsg"));
return error;
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
return null;
}
public LoginResult getAccessToken(String code,String appid ,String secret) {
StringBuilder uri = new StringBuilder();
uri.append(WeixinLoginConstants.HOST_API + WeixinLoginConstants.ACCESS_TOKEN + "?appid=");
if (Common.isNotNull(appid) && Common.isNotNull(secret)){
uri.append(appid);
uri.append("&secret=").append(secret);
}else {
uri.append(properties.getAppid());
uri.append("&secret=").append(properties.getAppsecret());
}
uri.append("&code=" + code).append("&grant_type=authorization_code");
try {
String url = uri.toString();
String result = restTemplate.getForObject(url, String.class);
JSONObject json = JSONUtil.parseObj(result);
if(json.getStr("access_token") != null){
WeixinAccessToken accessToken = new WeixinAccessToken();
accessToken.setAccess_token(json.getStr("access_token"));
accessToken.setOpenid(json.getStr("open_id"));
accessToken.setExpires_in(json.getLong("expires_in"));
accessToken.setRefresh_token(json.getStr("refresh_token"));
accessToken.setScope(json.getStr("scope"));
return accessToken;
}else{
WeixinError error = new WeixinError();
error.setErrcode(json.getStr("errcode"));
error.setErrmsg(json.getStr("errmsg"));
return error;
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
return null;
}
public LoginResult getWxUserInfo(WeixinAccessToken accessToken) {
StringBuilder uri = new StringBuilder();
uri.append(WeixinLoginConstants.HOST_API + WeixinLoginConstants.USER_INFO + "?access_token=");
uri.append(accessToken.getAccess_token());
uri.append("&openid=" + accessToken.getOpenid());
uri.append("&lang=zh_CN");
try {
String result = restTemplate.getForObject(uri.toString(), String.class);
WeixinUserInfo userInfo = JSONUtil.toBean(result, WeixinUserInfo.class);
if(userInfo != null && null != userInfo.getOpenid()){
return userInfo;
}else{
WeixinError error = JSONUtil.toBean(result, WeixinError.class);
return error;
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
return null;
}
@Override
public String loadAuthPage(String state) {
return null;
}
}
package com.yifu.cloud.plus.v1.yifu.auth.vo;
import com.yifu.cloud.plus.v1.yifu.admin.api.entity.SysUser;
import com.yifu.cloud.plus.v1.yifu.auth.util.weixin.model.WeixinUserInfo;
import lombok.Data;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
/**
* @program: master
* @description: 微信返回包装类
* @author: pwang
* @create: 2020-08-10 21:11
**/
@Data
public class WxUserVo {
private OAuth2AccessToken oauth2AccessToken;
private WeixinUserInfo weixinUserInfo;
private SysUser user;
}
...@@ -36,3 +36,10 @@ spring: ...@@ -36,3 +36,10 @@ spring:
prefer-file-system-access: true prefer-file-system-access: true
suffix: .ftl suffix: .ftl
template-loader-path: classpath:/templates/ template-loader-path: classpath:/templates/
weixin:
login:
appid: wxfb3a14ee64c87046
appsecret: e2143c5a1f635e039122de27f29354fe
redirectUri: https://www.ngrok.xiaomiqiu.cn/admin/hi
errorUri: /error
callbackUri: https://www.ngrok.xiaomiqiu.cn/oauth/weixin/callback
...@@ -133,6 +133,7 @@ public interface CommonConstants { ...@@ -133,6 +133,7 @@ public interface CommonConstants {
String FIVE_STRING = "5"; String FIVE_STRING = "5";
String TWELVE_STRING = "12"; String TWELVE_STRING = "12";
int TWELVE_INT = 12;
String THIRTEEN_STRING = "13"; String THIRTEEN_STRING = "13";
...@@ -530,4 +531,10 @@ public interface CommonConstants { ...@@ -530,4 +531,10 @@ public interface CommonConstants {
// C端前缀 // C端前缀
public static final String C_PREFIX = CacheConstants.DEFAULT_CODE_KEY.concat("_C_"); public static final String C_PREFIX = CacheConstants.DEFAULT_CODE_KEY.concat("_C_");
/**
* 用户未注册
*/
public static final Integer REGISTERED = 3;
} }
...@@ -91,6 +91,9 @@ public class R<T> implements Serializable { ...@@ -91,6 +91,9 @@ public class R<T> implements Serializable {
public static <T> R<T> other(int code, String msg) { public static <T> R<T> other(int code, String msg) {
return restResult(null, code, msg); return restResult(null, code, msg);
} }
public static <T> R<T> other(T data, String msg, int code) {
return restResult(data, code, msg);
}
private static <T> R<T> restResult(T data, int code, String msg) { private static <T> R<T> restResult(T data, int code, String msg) {
R<T> apiResult = new R<>(); R<T> apiResult = new R<>();
......
package com.yifu.cloud.plus.v1.yifu.common.core.util.http;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
/**
* 获取http会话数据的工具类
*
* @author daijie
* @since 2017年6月5日
*/
public class HttpConversationUtil {
private static Logger logger = LoggerFactory.getLogger(HttpConversationUtil.class);
/**
* 获取当前请求会话
* @return HttpServletRequest
*/
public static HttpServletRequest getRequest(){
return ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();
}
/**
* 获取当前响应会话
* @return HttpServletResponse
*/
public static HttpServletResponse getResponse(){
return ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getResponse();
}
/**
* 获取当前请求会话IP
* @return String
*/
public static String getIP(){
String ip = getRequest().getHeader("x-forwarded-for");
if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){
ip = getRequest().getHeader("Proxy-Client-IP");
}
if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){
ip = getRequest().getHeader("WL-Proxy-Client-IP");
}
if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){
ip = getRequest().getRemoteAddr();
}
return ip.equals("0:0:0:0:0:0:0:1")?"127.0.0.1":ip;
}
/**
* 获取请求Body转换为json字符串
*
* @return String
*/
public static String getBodyString() {
StringBuilder sb = new StringBuilder();
InputStream inputStream = null;
BufferedReader reader = null;
try {
inputStream = getRequest().getInputStream();
reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8")));
String line = "";
while ((line = reader.readLine()) != null) {
sb.append(line);
}
} catch (IOException e) {
logger.info("未读取到请求body中数据");
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
logger.info("未读取到请求body中数据");
}
}
}
return sb.toString();
}
/**
* 得到所有的参数的值
* @return Map
*/
public static Map<String,Object> handelRequest(){
Map<String,Object> obj = new HashMap<String,Object>();
Enumeration<String> coll = getRequest().getParameterNames();
while(coll.hasMoreElements()){
String key = coll.nextElement();
obj.put(key, getRequest().getParameter(key)==null?"":getRequest().getParameter(key) );
}
if(obj.get("pageSize")!=null || obj.get("pageNumber")!=null){
int pageSize = Integer.valueOf(obj.get("pageSize").toString());
int pageNumber = Integer.valueOf(obj.get("pageNumber").toString());
int startIndex = (pageNumber - 1) * pageSize;
obj.put("startIndex", startIndex);
obj.put("pageSize", pageSize);
obj.put("pageNumber", pageNumber);
}
return obj;
}
}
package com.yifu.cloud.plus.v1.yifu.common.core.util.http.htapi;
import com.yifu.cloud.plus.v1.yifu.common.core.constant.CommonConstants;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
/**
* 仿JS 编码 解码
* @Author fxj
* @Date 2020-11-13
* @return
**/
public class EncodingUtil {
public static String decodeURIComponent(String s) {
if (s == null) {
return null;
}
String result = null;
try {
result = URLDecoder.decode(s, CommonConstants.UTF8);
}
// This exception should never occur.
catch (UnsupportedEncodingException e) {
result = s;
}
return result;
}
public static String encodeURIComponent(String s) {
String result = null;
try {
result = URLEncoder.encode(s, CommonConstants.UTF8)
.replaceAll("\\+", "%20")
.replaceAll("\\%21", "!")
.replaceAll("\\%27", "'")
.replaceAll("\\%28", "(")
.replaceAll("\\%29", ")")
.replaceAll("\\%7E", "~");
}
// This exception should never occur.
catch (UnsupportedEncodingException e) {
result = s;
}
return result;
}
private EncodingUtil() {
super();
}
}
package com.yifu.cloud.plus.v1.yifu.common.core.util.http.htapi;
import lombok.Data;
import java.io.Serializable;
/**
* API 返回结果
* @Author fxj
* @Date 2020-11-13
* @return
**/
@Data
public class ResultBean implements Serializable {
/**
* API返回结果
**/
private String ENCMSG;
}
...@@ -17,10 +17,13 @@ ...@@ -17,10 +17,13 @@
package com.yifu.cloud.plus.v1.yifu.common.core.vo; package com.yifu.cloud.plus.v1.yifu.common.core.vo;
import lombok.Getter; import lombok.Getter;
import lombok.Setter;
import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.User;
import java.util.Collection; import java.util.Collection;
import java.util.List;
import java.util.Map;
/** /**
* @author lengleng * @author lengleng
...@@ -91,4 +94,11 @@ public class YifuUser extends User { ...@@ -91,4 +94,11 @@ public class YifuUser extends User {
this.ldapDn = ldapDn; this.ldapDn = ldapDn;
} }
/**
* key是客户端id,val是角色id数组
*/
@Getter
@Setter
private Map<String, List<Integer>> clientRoleMap;
} }
...@@ -65,7 +65,7 @@ public class YifuResourceServerConfigurerAdapter extends ResourceServerConfigure ...@@ -65,7 +65,7 @@ public class YifuResourceServerConfigurerAdapter extends ResourceServerConfigure
permitAllUrl.getUrls().forEach(url -> registry.antMatchers(url).permitAll()); permitAllUrl.getUrls().forEach(url -> registry.antMatchers(url).permitAll());
// “/user/getInfoByUsername" URL 访问放行 // “/user/getInfoByUsername" URL 访问放行
registry.and() registry.and()
.authorizeRequests().antMatchers("/api/**","/api-docs/**").permitAll() .authorizeRequests().antMatchers("/api/**","/api-docs/**","/**/getInfoByEmpPhone","/**/sendCCode","/**/employee/registration").permitAll()
.anyRequest().authenticated().and().csrf().disable(); .anyRequest().authenticated().and().csrf().disable();
} }
......
...@@ -186,6 +186,11 @@ public class TStatisticsCurrentReport { ...@@ -186,6 +186,11 @@ public class TStatisticsCurrentReport {
@HeadFontStyle(fontHeightInPoints = 11) @HeadFontStyle(fontHeightInPoints = 11)
@ExcelProperty("累计3岁以下婴幼儿照护") @ExcelProperty("累计3岁以下婴幼儿照护")
private BigDecimal sumBabyMoney; private BigDecimal sumBabyMoney;
@ExcelAttribute(name = "累计个人养老金", isFloat = true,max = "999999999.99")
@HeadFontStyle(fontHeightInPoints = 11)
@ExcelProperty(value = "累计个人养老金")
private BigDecimal sumPrivatePension;
/** /**
* 是否当月新增人员 * 是否当月新增人员
*/ */
...@@ -299,9 +304,4 @@ public class TStatisticsCurrentReport { ...@@ -299,9 +304,4 @@ public class TStatisticsCurrentReport {
@ExcelProperty("专项汇总") @ExcelProperty("专项汇总")
private BigDecimal specialDeduMoney; private BigDecimal specialDeduMoney;
@ExcelAttribute(name = "累计个人养老金", isFloat = true,max = "999999999.99")
@HeadFontStyle(fontHeightInPoints = 11)
@ExcelProperty(value = "累计个人养老金")
private BigDecimal sumPrivatePension;
} }
...@@ -264,6 +264,11 @@ public class TStatisticsCurrentReportMarketVo implements Serializable { ...@@ -264,6 +264,11 @@ public class TStatisticsCurrentReportMarketVo implements Serializable {
@HeadFontStyle(fontHeightInPoints = 11) @HeadFontStyle(fontHeightInPoints = 11)
@ExcelProperty("累计3岁以下婴幼儿照护") @ExcelProperty("累计3岁以下婴幼儿照护")
private BigDecimal sumBabyMoney; private BigDecimal sumBabyMoney;
@ExcelAttribute(name = "累计个人养老金", isFloat = true,max = "999999999.99")
@HeadFontStyle(fontHeightInPoints = 11)
@ExcelProperty(value = "累计个人养老金")
private BigDecimal sumPrivatePension;
/** /**
* 累计其他扣除 * 累计其他扣除
*/ */
......
...@@ -47,7 +47,7 @@ public class SalaryUploadController { ...@@ -47,7 +47,7 @@ public class SalaryUploadController {
@Operation(description = "打开结算单") @Operation(description = "打开结算单")
@SysLog("打开结算单") @SysLog("打开结算单")
@PostMapping("openBill") @PostMapping("/openBill")
public R<TSalaryDetailVo> openBill(@RequestParam String salaryFormId) { public R<TSalaryDetailVo> openBill(@RequestParam String salaryFormId) {
return salaryUploadService.getBill(salaryFormId); return salaryUploadService.getBill(salaryFormId);
} }
......
...@@ -158,8 +158,8 @@ public class TSalaryAccountPhoneController { ...@@ -158,8 +158,8 @@ public class TSalaryAccountPhoneController {
* @return * @return
*/ */
@Operation(description = "根据手机号获取员工信息") @Operation(description = "根据手机号获取员工信息")
@GetMapping("/info/{empPhone}") @GetMapping("/getInfoByEmpPhone")
public R<TSalaryEmployee> getWxEmployeeInfo(@PathVariable String empPhone) { public R<TSalaryEmployee> getInfoByEmpPhone(@RequestParam String empPhone) {
if (Common.isEmpty(empPhone)) { if (Common.isEmpty(empPhone)) {
return R.failed("手机号不能为空"); return R.failed("手机号不能为空");
} }
......
...@@ -690,7 +690,7 @@ public class SalaryUploadServiceImpl extends ServiceImpl<TSalaryStandardMapper, ...@@ -690,7 +690,7 @@ public class SalaryUploadServiceImpl extends ServiceImpl<TSalaryStandardMapper,
//累计个人养老 //累计个人养老
sdSum = sdSum.add(this.saveNewItems(saiList, SalaryConstants.SUM_PRIVATE_PENSION, sdSum = sdSum.add(this.saveNewItems(saiList, SalaryConstants.SUM_PRIVATE_PENSION,
SalaryConstants.SUM_PRIVATE_PENSION_JAVA, sds.getSumPrivatePension(), CommonConstants.ZERO_INT)); SalaryConstants.SUM_PRIVATE_PENSION_JAVA, sds.getSumPrivatePension(), CommonConstants.ZERO_INT));
a.setSumPrivatePension(sds.getSumBabyMoney()); a.setSumPrivatePension(sds.getSumPrivatePension());
//累计专项扣除总额 //累计专项扣除总额
this.saveNewItems(saiList, SalaryConstants.SPECIAL_DEDU_MONEY_SUM, this.saveNewItems(saiList, SalaryConstants.SPECIAL_DEDU_MONEY_SUM,
SalaryConstants.SPECIAL_DEDU_MONEY_SUM_JAVA, sdSum, CommonConstants.ZERO_INT); SalaryConstants.SPECIAL_DEDU_MONEY_SUM_JAVA, sdSum, CommonConstants.ZERO_INT);
......
...@@ -55,10 +55,10 @@ ...@@ -55,10 +55,10 @@
<result property="unitName" column="UNIT_NAME"/> <result property="unitName" column="UNIT_NAME"/>
<result property="specialDeduMoney" column="SPECIAL_DEDU_MONEY"/> <result property="specialDeduMoney" column="SPECIAL_DEDU_MONEY"/>
<result property="sumBabyMoney" column="sum_baby_money"/> <result property="sumBabyMoney" column="sum_baby_money"/>
<result property="sumPrivatePension" column="SUM_PRIVATE_PENSION"/>
<result property="isNewEmployee" column="IS_NEW_EMPLOYEE"/> <result property="isNewEmployee" column="IS_NEW_EMPLOYEE"/>
<result property="createMonth" column="CREATE_MONTH"/> <result property="createMonth" column="CREATE_MONTH"/>
<result property="empPhone" column="EMP_PHONE"/> <result property="empPhone" column="EMP_PHONE"/>
<result property="sumPrivatePension" column="SUM_PRIVATE_PENSION"/>
</resultMap> </resultMap>
<sql id="Base_Column_List"> <sql id="Base_Column_List">
a.id, a.id,
...@@ -92,10 +92,10 @@ ...@@ -92,10 +92,10 @@
a.UNIT_NAME, a.UNIT_NAME,
a.SPECIAL_DEDU_MONEY, a.SPECIAL_DEDU_MONEY,
a.sum_baby_money, a.sum_baby_money,
a.SUM_PRIVATE_PENSION,
a.IS_NEW_EMPLOYEE, a.IS_NEW_EMPLOYEE,
a.CREATE_MONTH, a.CREATE_MONTH,
a.EMP_PHONE, a.EMP_PHONE
a.SUM_PRIVATE_PENSION
</sql> </sql>
<sql id="tStatisticsCurrentReport_where"> <sql id="tStatisticsCurrentReport_where">
<if test="tStatisticsCurrentReport != null"> <if test="tStatisticsCurrentReport != null">
......
...@@ -23,6 +23,7 @@ import lombok.Data; ...@@ -23,6 +23,7 @@ import lombok.Data;
import java.io.Serializable; import java.io.Serializable;
import java.util.List; import java.util.List;
import java.util.Map;
/** /**
* @author lengleng * @author lengleng
...@@ -44,6 +45,11 @@ public class UserInfo implements Serializable { ...@@ -44,6 +45,11 @@ public class UserInfo implements Serializable {
*/ */
private String[] permissions; private String[] permissions;
/**
* key是客户端id,val是角色id数组
*/
private Map<String,List<Integer>> clientRoleMap;
/** /**
* 角色集合 * 角色集合
*/ */
......
...@@ -451,4 +451,21 @@ public class UserController { ...@@ -451,4 +451,21 @@ public class UserController {
userService.saveCUser(employeeInfo.getEmpIdcard(), employeeInfo.getEmpName(), employeeInfo.getEmpPhone(), openId, headImgUrl); userService.saveCUser(employeeInfo.getEmpIdcard(), employeeInfo.getEmpName(), employeeInfo.getEmpPhone(), openId, headImgUrl);
return R.ok(); return R.ok();
} }
/**
* 根据openid和类型获取指定用户全部信息
*
* @return 用户信息
*/
@Inner
@PostMapping("/inner/getSysUserByWxOpenId")
public UserInfo getSysUserByWxOpenId(@RequestBody SysUser users) {
SysUser user = userService.getOne(Wrappers.<SysUser>query()
.lambda().eq(SysUser::getWxOpenid, users.getWxOpenid()).last(CommonConstants.LAST_ONE_SQL));
if (user == null) {
return null;
}
UserInfo info = userService.getUserInfo(user);
return info;
}
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment