Эх сурвалжийг харах

添加微信登录授权逻辑

silent 4 жил өмнө
parent
commit
a5b020ab74

+ 80 - 30
src/main/java/org/springblade/gateway/login_gateway/controller/LoginController.java

@@ -1,18 +1,18 @@
 package org.springblade.gateway.login_gateway.controller;
 
 import cn.hutool.core.collection.CollUtil;
-import cn.hutool.core.convert.Convert;
 import cn.hutool.core.lang.Assert;
 import cn.hutool.core.map.MapUtil;
-import cn.hutool.core.util.ObjectUtil;
-import cn.hutool.core.util.StrUtil;
+import com.alibaba.fastjson.JSONObject;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
-import com.sun.xml.bind.v2.TODO;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import io.swagger.annotations.ApiParam;
 import lombok.AllArgsConstructor;
+import lombok.SneakyThrows;
+import org.apache.commons.lang3.ObjectUtils;
+import org.apache.commons.lang3.StringUtils;
 import org.springblade.common.utils.BeanPropertyUtil;
 import org.springblade.core.mp.support.Condition;
 import org.springblade.core.tool.api.R;
@@ -27,6 +27,9 @@ import org.springblade.sing.point.entity.UserPufaPoint;
 import org.springblade.sing.point.service.IUserPufaPointService;
 import org.springblade.sing.user.entity.LoginUser;
 import org.springblade.sing.user.service.ILoginUserService;
+import org.springblade.wx.constant.MiniProgramType;
+import org.springblade.wx.constant.ScopeTypeEnum;
+import org.springblade.wx.service.WeChatService;
 import org.springframework.web.bind.annotation.*;
 
 import javax.validation.Valid;
@@ -48,24 +51,26 @@ public class LoginController {
 	private final IUserPufaPointService userPufaPointService;
 	private final IUserHeatService userHeatService;
 	private final IActiveProductRecordService activeProductRecordService;
+	private final WeChatService weChatService;
 
 	/**
 	 * 小程序端全部返回false
 	 * 判断是否为移动用户
 	 */
 	@GetMapping("/isCMCC")
-	public R<Boolean> isCMCC(@ApiParam(value = "手机号",required = true) @RequestParam String phone){
+	public R<Boolean> isCMCC(@ApiParam(value = "手机号", required = true) @RequestParam String phone) {
 //		loginService.isCMCC(phone)
 		return R.data(false);
 	}
 
 	/**
 	 * H5 正常校验
+	 *
 	 * @param phone
 	 * @return
 	 */
 	@GetMapping("/h5IsCMCC")
-	public R<Boolean> h5IsCMCC(@ApiParam(value = "手机号",required = true) @RequestParam String phone){
+	public R<Boolean> h5IsCMCC(@ApiParam(value = "手机号", required = true) @RequestParam String phone) {
 		return R.data(loginService.isCMCC(phone));
 	}
 
@@ -73,8 +78,8 @@ public class LoginController {
 	 * 保存用户的手机号
 	 */
 	@PostMapping("/savePhone")
-	public R<Boolean> savePhone(@RequestParam String phone,@RequestParam Long userId){
-		synchronized (phone.intern()){
+	public R<Boolean> savePhone(@RequestParam String phone, @RequestParam Long userId) {
+		synchronized (phone.intern()) {
 			List<LoginUser> list = loginUserService.list(Condition.getQueryWrapper(new LoginUser()).lambda().eq(LoginUser::getPhone, phone));
 			Assert.isTrue(CollUtil.isEmpty(list), "该手机号已被绑定");
 			LoginUser loginUser = loginUserService.getById(userId);
@@ -89,14 +94,14 @@ public class LoginController {
 	 */
 	@PostMapping("/updatePhone")
 	public R updatePhone(@RequestParam String oldPhone,
-								  @RequestParam String newPhone,
-								  @RequestParam String code,
-								  @RequestParam String smsId,
-								  @RequestParam Long userId){
+						 @RequestParam String newPhone,
+						 @RequestParam String code,
+						 @RequestParam String smsId,
+						 @RequestParam Long userId) {
 		LoginUser loginUser = loginUserService.getById(userId);
 		Assert.notNull(loginUser, "找不到该用户");
 		Assert.isTrue(Objects.equals(oldPhone, loginUser.getPhone()), "原手机号有误");
-		Assert.isTrue(SmsUtil.validateMessage(SmsCodeEnum.VALIDATE.name(), smsId, code, newPhone),SmsUtil.VALIDATE_FAIL);
+		Assert.isTrue(SmsUtil.validateMessage(SmsCodeEnum.VALIDATE.name(), smsId, code, newPhone), SmsUtil.VALIDATE_FAIL);
 		List<LoginUser> list = loginUserService.list(Condition.getQueryWrapper(new LoginUser()).lambda().eq(LoginUser::getPhone, newPhone));
 		Assert.isTrue(CollUtil.isEmpty(list), "该手机号已被绑定");
 		loginUser.setPhone(newPhone);
@@ -110,9 +115,9 @@ public class LoginController {
 	@ApiOperationSupport(order = 6)
 	@ApiOperation(value = "用户登录", notes = "传入loginUser")
 	public R login(@Valid @RequestBody LoginUser loginUser) {
-		synchronized (loginUser.getOpenid().intern()){
-			List<LoginUser> users = loginUserService.list(Condition.getQueryWrapper(new LoginUser()).lambda().eq(LoginUser::getOpenid, loginUser.getOpenid()).orderByDesc(LoginUser::getCreateTime));
-			if(CollUtil.isEmpty(users)){
+		synchronized (loginUser.getOpenid().intern()) {
+			List<LoginUser> users = loginUserService.list(Wrappers.<LoginUser>lambdaQuery().eq(LoginUser::getOpenid, loginUser.getOpenid()).orderByDesc(LoginUser::getCreateTime));
+			if (CollUtil.isEmpty(users)) {
 				LoginUser user = new LoginUser();
 				user.setAvatar(loginUser.getAvatar());
 				user.setNickName(loginUser.getNickName());
@@ -125,17 +130,64 @@ public class LoginController {
 		}
 	}
 
+	/**
+	 * 获取微信授权URL
+	 */
+	@GetMapping("/getH5WeixinAuthorizeUrl")
+	@ApiOperationSupport(order = 6)
+	@ApiOperation(value = "获取微信授权URL")
+	@SneakyThrows
+	public R getH5WeixinAuthorizeUrl(@ApiParam("重定向地址") @RequestParam String redirectUri) {
+		return R.data(weChatService.getWeiXinH5Oauth2AuthorizeUrl(MiniProgramType.OFFICIAL, redirectUri, ScopeTypeEnum.sapi_userinfo));
+	}
+
+	/**
+	 * 获取微信授权URL
+	 */
+	@GetMapping("/getWeiXinH5UserInfo")
+	@ApiOperationSupport(order = 6)
+	@ApiOperation(value = "获取微信授权URL")
+	public R getWeiXinH5UserInfo(@ApiParam("微信授权Code") @RequestParam String code) {
+		String weiXinH5AccessToken = weChatService.getWeiXinH5AccessToken(MiniProgramType.OFFICIAL, code);
+		if (StringUtils.isEmpty(weiXinH5AccessToken)) {
+			return R.fail("获取用户信息失败");
+		}
+		JSONObject weiXinH5UserInfo = weChatService.getWeiXinH5UserInfo(weiXinH5AccessToken);
+		if (ObjectUtils.isEmpty(weiXinH5UserInfo)) {
+			return R.fail("获取用户信息失败");
+		}
+		LoginUser loginUser = new LoginUser() {{
+			setOpenid(weiXinH5UserInfo.getString(BeanPropertyUtil.getFieldName(LoginUser::getOpenid).toLowerCase()));
+			setNickName(weiXinH5UserInfo.getString(BeanPropertyUtil.getFieldName(LoginUser::getNickName).toLowerCase()));
+			setAvatar(weiXinH5UserInfo.getString("headimgurl"));
+			String sex = weiXinH5UserInfo.getString(BeanPropertyUtil.getFieldName(LoginUser::getSex).toLowerCase());
+			switch (sex) {
+				case "1":
+					sex = "男";
+					break;
+				case "2":
+					sex = "女";
+					break;
+				default:
+					sex = "未知";
+					break;
+			}
+			setSex(sex);
+		}};
+		return login(loginUser);
+	}
+
 	/**
 	 * h5登录
 	 */
 	@PostMapping("/h5Login")
 	@ApiOperationSupport(order = 6)
 	@ApiOperation(value = "h5登录", notes = "传入loginUser")
-	public R h5Login(@RequestParam String phone,@RequestParam String code,@RequestParam String smsId) {
-		Assert.isTrue(SmsUtil.validateMessage(SmsCodeEnum.VALIDATE.name(), smsId, code, phone),SmsUtil.VALIDATE_FAIL);
-		synchronized (phone.intern()){
+	public R h5Login(@RequestParam String phone, @RequestParam String code, @RequestParam String smsId) {
+		Assert.isTrue(SmsUtil.validateMessage(SmsCodeEnum.VALIDATE.name(), smsId, code, phone), SmsUtil.VALIDATE_FAIL);
+		synchronized (phone.intern()) {
 			List<LoginUser> users = loginUserService.list(Condition.getQueryWrapper(new LoginUser()).lambda().eq(LoginUser::getPhone, phone).orderByDesc(LoginUser::getCreateTime));
-			if(CollUtil.isEmpty(users)){
+			if (CollUtil.isEmpty(users)) {
 				LoginUser user = new LoginUser();
 				user.setPhone(phone);
 				//给h5设置默认的头像和昵称
@@ -149,18 +201,16 @@ public class LoginController {
 	}
 
 
-
-
 	/**
 	 * 查询用户积分
 	 */
 	@PostMapping("/userPufaPoint")
 	@ApiOperationSupport(order = 7)
 	@ApiOperation(value = "查询用户普法积分")
-	public R<UserPufaPoint> userPufaPoint(@ApiParam(value = "用户手机号码",required = true) @RequestParam String phone) {
+	public R<UserPufaPoint> userPufaPoint(@ApiParam(value = "用户手机号码", required = true) @RequestParam String phone) {
 		return R.data(userPufaPointService.getOne(
 			Wrappers.<UserPufaPoint>lambdaQuery()
-				.eq(UserPufaPoint::getPhone,phone)));
+				.eq(UserPufaPoint::getPhone, phone)));
 	}
 
 	/**
@@ -169,10 +219,10 @@ public class LoginController {
 	@PostMapping("/userHeatValue")
 	@ApiOperationSupport(order = 8)
 	@ApiOperation(value = "查询用户热力值")
-	public R<UserHeat> userHeatValue(@ApiParam(value = "用户手机号码",required = true) @RequestParam String phone) {
+	public R<UserHeat> userHeatValue(@ApiParam(value = "用户手机号码", required = true) @RequestParam String phone) {
 		return R.data(userHeatService.getOne(
 			Wrappers.<UserHeat>lambdaQuery()
-				.eq(UserHeat::getPhone,phone)));
+				.eq(UserHeat::getPhone, phone)));
 	}
 
 	/**
@@ -181,14 +231,14 @@ public class LoginController {
 	@PostMapping("/userVoteCount")
 	@ApiOperationSupport(order = 8)
 	@ApiOperation(value = "查询参赛用户票数")
-	public R<Long> userVoteCount(@ApiParam(value = "用户手机号码",required = true) @RequestParam String phone) {
+	public R<Long> userVoteCount(@ApiParam(value = "用户手机号码", required = true) @RequestParam String phone) {
 		Map<String, Object> map = activeProductRecordService.getMap(Wrappers.<ActiveProductRecord>query().select(new StringBuilder("ifnull(sum(")
 				.append(BeanPropertyUtil.getFieldNameToUnder(ActiveProductRecord::getVoteCount))
 				.append("),0) as count")
 				.toString())
 			.lambda()
 			.eq(ActiveProductRecord::getPhone, phone));
-		return R.data(MapUtil.getLong(map,"count"));
+		return R.data(MapUtil.getLong(map, "count"));
 	}
 
 	/**
@@ -197,11 +247,11 @@ public class LoginController {
 	@GetMapping("/userHeatValueAndPufaPoint")
 	@ApiOperationSupport(order = 8)
 	@ApiOperation(value = "查询用户热力值和普法积分")
-	public R<Map<String,?>> userHeatValueAndPufaPoint(@ApiParam(value = "用户手机号码",required = true) @RequestParam String phone) {
+	public R<Map<String, ?>> userHeatValueAndPufaPoint(@ApiParam(value = "用户手机号码", required = true) @RequestParam String phone) {
 		Map<String, Object> hashMap = new HashMap<>();
 		hashMap.put("userHeatValue", Optional.ofNullable(userHeatValue(phone).getData()).map(UserHeat::getHeatValue).orElse(BigDecimal.ZERO));
 		hashMap.put("userPufaPoint", Optional.ofNullable(userPufaPoint(phone).getData()).map(UserPufaPoint::getPufaPoint).orElse(BigDecimal.ZERO));
-		hashMap.put("userVoteCount",Optional.ofNullable(userVoteCount(phone).getData()).orElse(0L));
+		hashMap.put("userVoteCount", Optional.ofNullable(userVoteCount(phone).getData()).orElse(0L));
 		return R.data(hashMap);
 	}
 }

+ 23 - 0
src/main/java/org/springblade/wx/constant/ScopeTypeEnum.java

@@ -0,0 +1,23 @@
+package org.springblade.wx.constant;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * @Author: Silent
+ * @Description
+ * @Date: Created in 11:16 2021/12/3
+ * @Modified By:
+ */
+@ApiModel("微信Scope类型")
+@AllArgsConstructor
+@Getter
+public enum ScopeTypeEnum {
+	snsapi_base("获取OpenId"),
+	sapi_userinfo("获取用户信息");
+
+    @ApiModelProperty("描述")
+    private String describe;
+}

+ 14 - 0
src/main/java/org/springblade/wx/constant/WeChatApi.java

@@ -23,6 +23,20 @@ public enum WeChatApi {
 	 * */
 	fetchAccessToken("https://api.weixin.qq.com/cgi-bin/token","GET"),
 
+	/**
+	 * 获取微信用户H5授权链接
+	 * */
+	getWeiXinH5Oauth2AuthorizeUrl("https://open.weixin.qq.com/connect/oauth2/authorize","GET"),
+
+	/**
+	 * 获取微信用户H5 AccessToken
+	 * */
+	getWeiXinH5AccessToken("https://api.weixin.qq.com/sns/oauth2/access_token","GET"),
+
+	/**
+	 * 获取微信用户H5 AccessToken
+	 * */
+	getWeiXinH5UserInfo("https://api.weixin.qq.com/sns/userinfo","GET"),
 
 	/**
 	 * OCR-营业执照

+ 121 - 25
src/main/java/org/springblade/wx/service/WeChatService.java

@@ -6,6 +6,8 @@ import cn.hutool.http.HttpUtil;
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONObject;
 import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.ObjectUtils;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.http.protocol.HTTP;
 import org.springblade.common.cache.WechatCache;
 import org.springblade.core.log.exception.ServiceException;
@@ -16,11 +18,14 @@ import org.springblade.core.tool.utils.StringUtil;
 import org.springblade.wx.config.*;
 import org.springblade.wx.constant.MessageTemplate;
 import org.springblade.wx.constant.MiniProgramType;
+import org.springblade.wx.constant.ScopeTypeEnum;
 import org.springblade.wx.constant.WeChatApi;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.util.Assert;
 
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Objects;
@@ -43,54 +48,141 @@ public class WeChatService {
 	/**
 	 * 小程序发送模板消息
 	 *
-	 * @return*/
-	public HttpResponse sendTemplateMessage(String openId, MessageTemplate messageTemplate,JSONObject content, String page, MiniProgramType miniProgramType){
+	 * @return
+	 */
+	public HttpResponse sendTemplateMessage(String openId, MessageTemplate messageTemplate, JSONObject content, String page, MiniProgramType miniProgramType) {
 
-		Assert.notNull(messageTemplate,()->{ throw new ServiceException("找不到响应的消息模板"); });
+		Assert.notNull(messageTemplate, () -> {
+			throw new ServiceException("找不到响应的消息模板");
+		});
 		String token = fetchAccessToken(miniProgramType);
-		String query = "?access_token="+token;
+		String query = "?access_token=" + token;
 		HttpResponse execute = HttpRequest.post(WeChatApi.sendTemplateMessage.getApi() + query)
 			.body(messageTemplate.buildRequestBody(openId, content, page))
 			.execute();
-		log.info("微信发送消息模板响应:{}",execute.body());
-		return  execute;
+		log.info("微信发送消息模板响应:{}", execute.body());
+		return execute;
 	}
 
 	/**
-	 * 	获取小程序的AccessToken
-	 * */
-	public String fetchAccessToken(MiniProgramType type){
+	 * 获取小程序的AccessToken
+	 */
+	public String fetchAccessToken(MiniProgramType type) {
 
 		WeChatConfig config = getConfig(type);
 		String accessToken = WechatCache.getAccessToken(config.getAppId());
 
 		//缓存没有请求微信
-		if(Objects.isNull(accessToken)){
+		if (Objects.isNull(accessToken)) {
 			HttpResponse response = HttpRequest.get(WeChatApi.fetchAccessToken.getApi())
 				.form("grant_type", "client_credential")
 				.form("appid", config.getAppId())
 				.form("secret", config.getAppSecret())
 				.execute();
 			JSONObject jsonObject = JSON.parseObject(response.body());
-			log.info("微信获取AccessToken信息:{}",jsonObject);
+			log.info("微信获取AccessToken信息:{}", jsonObject);
 
 			accessToken = jsonObject.getString("access_token");
 
 			if (StringUtil.isNotBlank(accessToken)) {
-				WechatCache.putAccessToken(config.getAppId(),accessToken);
-			}else{
-				bladeLogger.error("微信获取AccessToken错误信息",jsonObject.toJSONString());
+				WechatCache.putAccessToken(config.getAppId(), accessToken);
+			} else {
+				bladeLogger.error("微信获取AccessToken错误信息", jsonObject.toJSONString());
 			}
 		}
 
 		return accessToken;
 	}
 
+	/**
+	 * 获取微信H5授权链接
+	 *
+	 * @param type
+	 * @param redirectUri 重定向地址
+	 * @return
+	 */
+	public String getWeiXinH5Oauth2AuthorizeUrl(MiniProgramType type, String redirectUri, ScopeTypeEnum scopeTypeEnum) throws UnsupportedEncodingException {
+
+		WeChatConfig config = getConfig(type);
+
+		//构建请求链接
+		StringBuilder stringBuilder = new StringBuilder(WeChatApi.getWeiXinH5Oauth2AuthorizeUrl.getApi())
+			.append("?appid=")
+			.append(config.getAppId())
+			.append("&redirect_uri=")
+			.append(URLEncoder.encode(redirectUri, "UTF-8"))
+			.append("&response_type=code&scope=")
+			.append(scopeTypeEnum.name())
+			.append("&state=STATE#wechat_redirect");
+
+		log.info("微信H5用户授权链接:{}", stringBuilder);
+
+		return stringBuilder.toString();
+	}
+
+	/**
+	 * 获取微信H5 AccessToken
+	 *
+	 * @param type
+	 * @param code
+	 * @return
+	 * @throws UnsupportedEncodingException
+	 */
+	public String getWeiXinH5AccessToken(MiniProgramType type, String code) {
+
+		WeChatConfig config = getConfig(type);
+
+		//请求微信
+		HttpResponse response = HttpRequest.get(WeChatApi.getWeiXinH5AccessToken.getApi())
+			.form("appid", "client_credential")
+			.form("secret", config.getAppSecret())
+			.form("code", code)
+			.form("grant_type", "authorization_code")
+			.execute();
+
+		JSONObject jsonObject = JSON.parseObject(response.body());
+		log.info("微信获取AccessToken信息:{}", jsonObject);
+
+		String access_token = jsonObject.getString("access_token");
+		if (StringUtils.isEmpty(access_token)) {
+			bladeLogger.error("微信获取AccessToken信息错误", response.body());
+		}
+
+		return access_token;
+	}
+
+	/**
+	 * 获取微信H5用户信息
+	 *
+	 * @param accessToken
+	 * @return
+	 */
+	public JSONObject getWeiXinH5UserInfo(String accessToken) {
+
+		//请求微信
+		HttpResponse response = HttpRequest.get(WeChatApi.getWeiXinH5UserInfo.getApi())
+			.form("access_token", accessToken)
+			.form("openid", "OPENID")
+			.form("lang", "zh_CN")
+			.execute();
+
+		JSONObject jsonObject = JSON.parseObject(response.body());
+		log.info("微信获取用户信息错误:{}", jsonObject);
+
+		if (StringUtils.isNotEmpty(jsonObject.getString("errcode"))) {
+			bladeLogger.error("微信获取用户信息错误", response.body());
+			return null;
+		}
+
+		return jsonObject;
+	}
+
 	/**
 	 * 获取openId
+	 *
 	 * @return
 	 */
-	public JSONObject getOpenId(String code,MiniProgramType type){
+	public JSONObject getOpenId(String code, MiniProgramType type) {
 		WeChatConfig config = getConfig(type);
 		String appId = config.getAppId();
 		String appSecret = config.getAppSecret();
@@ -110,9 +202,10 @@ public class WeChatService {
 
 	/**
 	 * OCR-营业执照
+	 *
 	 * @return
 	 */
-	public HttpResponse  ocrBizilicense(String imgUrl,MiniProgramType type){
+	public HttpResponse ocrBizilicense(String imgUrl, MiniProgramType type) {
 		String token = fetchAccessToken(type);
 		HttpResponse response = HttpRequest.post(WeChatApi.OCR_BIZLICENSE.buildUrl(imgUrl, token)).execute();
 		return response;
@@ -120,9 +213,10 @@ public class WeChatService {
 
 	/**
 	 * OCR-身份证
+	 *
 	 * @return
 	 */
-	public HttpResponse  ocrIdcard(String imgUrl,MiniProgramType type){
+	public HttpResponse ocrIdcard(String imgUrl, MiniProgramType type) {
 		String token = fetchAccessToken(type);
 		HttpResponse response = HttpRequest.post(WeChatApi.OCR_ID_CARD.buildUrl(imgUrl, token)).execute();
 		return response;
@@ -130,9 +224,10 @@ public class WeChatService {
 
 	/**
 	 * OCR-银行卡
+	 *
 	 * @return
 	 */
-	public HttpResponse  ocrBank(String imgUrl,MiniProgramType type){
+	public HttpResponse ocrBank(String imgUrl, MiniProgramType type) {
 		String token = fetchAccessToken(type);
 		HttpResponse response = HttpRequest.post(WeChatApi.OCR_BANK.buildUrl(imgUrl, token)).execute();
 		return response;
@@ -140,13 +235,14 @@ public class WeChatService {
 
 	/**
 	 * 生成小程序码--无数量限制
+	 *
 	 * @return
 	 */
-	public HttpResponse getUnlimitCode(String queryData,MiniProgramType type,String page){
+	public HttpResponse getUnlimitCode(String queryData, MiniProgramType type, String page) {
 		String token = fetchAccessToken(type);
-		Map<String,String> params = new HashMap(){{
-			put("page",page);
-			put("scene",queryData);
+		Map<String, String> params = new HashMap() {{
+			put("page", page);
+			put("scene", queryData);
 		}};
 		HttpResponse response = HttpRequest.post(WeChatApi.getUnLimit.buildUrl(token)).header(HTTP.CONTENT_TYPE, "application/json").body(JSON.toJSONString(params)).execute();
 
@@ -156,9 +252,9 @@ public class WeChatService {
 
 	/**
 	 * 获取对应小程序的配置文件
-	 * */
-	private WeChatConfig getConfig(MiniProgramType type){
-		switch (type){
+	 */
+	private WeChatConfig getConfig(MiniProgramType type) {
+		switch (type) {
 			case MALL:
 				return SpringUtil.getBean(ConfigForMall.class);
 			case SHOP: