diff --git a/luoo_common/src/main/java/api/StatusCode.java b/luoo_common/src/main/java/api/StatusCode.java index cd4487b..8583550 100644 --- a/luoo_common/src/main/java/api/StatusCode.java +++ b/luoo_common/src/main/java/api/StatusCode.java @@ -21,6 +21,8 @@ public enum StatusCode implements IErrorCode { USER_SENSITIVE_INFO(10008, "不能提交敏感信息"), APPLE_lOGIN_FAILED(10009, "apple id登录失败"), + WECHAT_lOGIN_FAILED(10011, "微信 id登录失败"), + APPLEID_MOBILE_UNBINDED(10010,"请先绑定appleid和手机号"), diff --git a/luoo_common/src/main/java/constants/Constants.java b/luoo_common/src/main/java/constants/Constants.java index fcbcb41..f2259dc 100644 --- a/luoo_common/src/main/java/constants/Constants.java +++ b/luoo_common/src/main/java/constants/Constants.java @@ -6,6 +6,10 @@ public class Constants { public static final String REDIS_KEY_IMAGE_CHECK_CODE = "redis_key_image_check_code_"; public static final String REDIS_KEY_MOBILE_CHECK_CODE = "redis_key_mobile_check_code_"; + public static final String REDIS_KEY_WECHAT_TOKEN = "redis_key_wechat_token_byopenid_"; + public static final String REDIS_KEY_WECHAT_REFRESH_TOKEN = "redis_key_wechat_refresh_token_byopenid_"; + + public static final String REDIS_KEY_USER_COLLECT_JOURNAL = "redis_key_user_collect_journal_"; public static final String REDIS_KEY_PAGE_JOURNAL_RESPONSE_DTO = "redis_key_page_journal_response_dto___"; @@ -69,4 +73,12 @@ public class Constants { public static final Integer LENGTH_512 = 512; public static final String IP_LOCATION_CHINA = "中国"; + + public static final String HTTPS_API_WEIXIN_QQ_COM_SNS_OAUTH2_ACCESS_TOKEN = "https://api.weixin.qq.com/sns/oauth2/access_token"; + + public static final String HTTPS_API_WEIXIN_QQ_COM_SNS_OAUTH2_REFRESH_TOKEN = "https://api.weixin.qq.com/sns/oauth2/refresh_token"; + + public static final String HTTPS_API_WEIXIN_QQ_COM_SNS_AUTH = "https://api.weixin.qq.com/sns/auth"; + + public static final String HTTPS_API_WEIXIN_QQ_JSAPI = "https://api.weixin.qq.com/cgi-bin/ticket/getticket"; } diff --git a/luoo_user/src/main/java/com/luoo/user/controller/WeChatAouth2Controller.java b/luoo_user/src/main/java/com/luoo/user/controller/WeChatAouth2Controller.java new file mode 100644 index 0000000..929122b --- /dev/null +++ b/luoo_user/src/main/java/com/luoo/user/controller/WeChatAouth2Controller.java @@ -0,0 +1,65 @@ +package com.luoo.user.controller; + +import annotation.GlobalInterceptor; +import annotation.VerifyParam; +import api.Result; +import api.StatusCode; +import com.luoo.user.dto.request.WeChatBindReq; +import com.luoo.user.dto.request.WeChatLoginReq; +import com.luoo.user.dto.request.WeChatShareReq; +import com.luoo.user.service.WeChatAouth2Service; +import dto.UserLoginDto; +import enums.RequestFrequencyTypeEnum; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; +import controller.BaseController; +import util.JwtUtil; + +import javax.servlet.http.HttpServletRequest; + +/** + * @author Revers. + * @date 2024/03/21 10:05 + **/ + +@CrossOrigin +@RestController +@RequestMapping("/wechat") +@Api(tags = "WechatLoginController") +public class WeChatAouth2Controller extends BaseController { + + @Autowired + private WeChatAouth2Service weChatAouth2Service; + + @ApiOperation(value = "分享获取微信token", notes = "成功后返回token,不成功返回null") + @PostMapping("/getShareAccessToken") + public Result getShareAccessToken(@RequestBody WeChatShareReq weChatShareReq) { + return weChatAouth2Service.getShareToken(weChatShareReq); + } + + @ApiOperation(value = "微信登录/注册",notes = "微信登录/注册,成功后返回authorization") + @PostMapping("/login") + @GlobalInterceptor(frequencyType = RequestFrequencyTypeEnum.HOUR, requestFrequencyThreshold = 12) + public Result getLoginAccessToken(HttpServletRequest request, + @RequestBody @VerifyParam WeChatLoginReq loginReq){ + return weChatAouth2Service.loginOrRegister(loginReq,getIpAddr(request)); + } + + + @ApiOperation(value = "绑定手机号",notes = "绑定手机号") + @PostMapping("/bindPhoneNumber") + @GlobalInterceptor(frequencyType = RequestFrequencyTypeEnum.HOUR, requestFrequencyThreshold = 12) + public Result bindPhoneNumber(@RequestHeader(value = "Authorization", required = true) String authorization, + @RequestBody @VerifyParam WeChatBindReq weChatBindReq){ + + UserLoginDto user = new JwtUtil().getUserLoginDto(authorization); + if(user == null) + return Result.failed(StatusCode.UNAUTHORIZED); + + return weChatAouth2Service.bindPhoneNumber(user,weChatBindReq); + } + + +} diff --git a/luoo_user/src/main/java/com/luoo/user/dao/UserInfoDao.java b/luoo_user/src/main/java/com/luoo/user/dao/UserInfoDao.java index 6a496dc..1898a36 100644 --- a/luoo_user/src/main/java/com/luoo/user/dao/UserInfoDao.java +++ b/luoo_user/src/main/java/com/luoo/user/dao/UserInfoDao.java @@ -3,6 +3,7 @@ package com.luoo.user.dao; import java.util.List; import java.util.Optional; +import com.luoo.user.pojo.User; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; @@ -17,7 +18,7 @@ import client.vo.SimpleUser; public interface UserInfoDao extends JpaRepository, JpaSpecificationExecutor { public UserInfo findByMobile(String mobile); - + public UserInfo findUserInfoByWxId(String wxId); public UserInfo findByAppleId(String appleId); @Modifying @Query(value = "update tb_user_info set follow_count=follow_count+? where id = ?", nativeQuery = true) diff --git a/luoo_user/src/main/java/com/luoo/user/dto/request/WeChatBindReq.java b/luoo_user/src/main/java/com/luoo/user/dto/request/WeChatBindReq.java new file mode 100644 index 0000000..87c07fb --- /dev/null +++ b/luoo_user/src/main/java/com/luoo/user/dto/request/WeChatBindReq.java @@ -0,0 +1,28 @@ +package com.luoo.user.dto.request; + +import annotation.VerifyParam; +import enums.VerifyRegexEnum; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.Setter; + +import java.io.Serializable; + +/** + * @author Revers. + * @date 2024/03/25 17:56 + **/ + +@Getter +@Setter +@ApiModel +public class WeChatBindReq implements Serializable { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty(name = "mobile", value = "手机号", required = true) + @VerifyParam(required = true, regex = VerifyRegexEnum.MOBILE) + private String mobile; + +} diff --git a/luoo_user/src/main/java/com/luoo/user/dto/request/WeChatLoginReq.java b/luoo_user/src/main/java/com/luoo/user/dto/request/WeChatLoginReq.java new file mode 100644 index 0000000..cf45201 --- /dev/null +++ b/luoo_user/src/main/java/com/luoo/user/dto/request/WeChatLoginReq.java @@ -0,0 +1,35 @@ +package com.luoo.user.dto.request; + +import annotation.VerifyParam; +import enums.VerifyRegexEnum; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Getter; +import lombok.Setter; + +import java.io.Serializable; + +/** + * @author Revers. + * @date 2024/03/25 17:26 + **/ + +@Getter +@Setter +@ApiModel +public class WeChatLoginReq implements Serializable { + + private static final long serialVersionUID = 1L; + + + @ApiModelProperty(name = "code", value = "code码", required = true) + @VerifyParam(required = true) + String code; + + @ApiModelProperty(name = "deviceId", value = "设备id", required = true) + @VerifyParam(required = true) + private String deviceId; + + @ApiModelProperty(name = "deviceBrand", value = "设备品牌", required = false) + String deviceBrand; +} diff --git a/luoo_user/src/main/java/com/luoo/user/dto/request/WeChatShareReq.java b/luoo_user/src/main/java/com/luoo/user/dto/request/WeChatShareReq.java new file mode 100644 index 0000000..25ca62b --- /dev/null +++ b/luoo_user/src/main/java/com/luoo/user/dto/request/WeChatShareReq.java @@ -0,0 +1,14 @@ +package com.luoo.user.dto.request; + +import lombok.Data; + +/** + * @author Revers. + * @date 2024/03/25 19:21 + **/ + +@Data +public class WeChatShareReq { + + private String code; +} diff --git a/luoo_user/src/main/java/com/luoo/user/dto/response/Aouth2WeChatErrorResponse.java b/luoo_user/src/main/java/com/luoo/user/dto/response/Aouth2WeChatErrorResponse.java new file mode 100644 index 0000000..6c1902e --- /dev/null +++ b/luoo_user/src/main/java/com/luoo/user/dto/response/Aouth2WeChatErrorResponse.java @@ -0,0 +1,15 @@ +package com.luoo.user.dto.response; + +import lombok.Data; + +/** + * @author Revers. + * @date 2024/03/25 18:09 + **/ + +@Data +public class Aouth2WeChatErrorResponse { + + private Integer errcode; + private String errmsg; +} diff --git a/luoo_user/src/main/java/com/luoo/user/dto/response/Aouth2WeChatJSAPIResponse.java b/luoo_user/src/main/java/com/luoo/user/dto/response/Aouth2WeChatJSAPIResponse.java new file mode 100644 index 0000000..6686fa3 --- /dev/null +++ b/luoo_user/src/main/java/com/luoo/user/dto/response/Aouth2WeChatJSAPIResponse.java @@ -0,0 +1,17 @@ +package com.luoo.user.dto.response; + +import lombok.Data; + +/** + * @author Revers. + * @date 2024/03/26 21:45 + **/ + +@Data +public class Aouth2WeChatJSAPIResponse { + + private Integer errcode; + private String errmsg; + private String ticket; + private Integer expires_in; +} diff --git a/luoo_user/src/main/java/com/luoo/user/dto/response/Aouth2WeChatTokenResponse.java b/luoo_user/src/main/java/com/luoo/user/dto/response/Aouth2WeChatTokenResponse.java new file mode 100644 index 0000000..1aadce1 --- /dev/null +++ b/luoo_user/src/main/java/com/luoo/user/dto/response/Aouth2WeChatTokenResponse.java @@ -0,0 +1,18 @@ +package com.luoo.user.dto.response; + +import lombok.Data; + +/** + * @author Revers. + * @date 2024/03/21 10:15 + **/ + +@Data +public class Aouth2WeChatTokenResponse { + private String access_token; + private Long expires_in; + private String refresh_token; + private String openid; + private String scope; + private String unionid; +} diff --git a/luoo_user/src/main/java/com/luoo/user/pojo/UserInfo.java b/luoo_user/src/main/java/com/luoo/user/pojo/UserInfo.java index 1f4e595..9cf1be0 100644 --- a/luoo_user/src/main/java/com/luoo/user/pojo/UserInfo.java +++ b/luoo_user/src/main/java/com/luoo/user/pojo/UserInfo.java @@ -44,6 +44,11 @@ public class UserInfo implements Serializable { */ private String appleId; + /** + * 微信 id + */ + private String wxId; + /** * 头像 */ diff --git a/luoo_user/src/main/java/com/luoo/user/service/UserInfoService.java b/luoo_user/src/main/java/com/luoo/user/service/UserInfoService.java index 27ba394..9fcc5d9 100644 --- a/luoo_user/src/main/java/com/luoo/user/service/UserInfoService.java +++ b/luoo_user/src/main/java/com/luoo/user/service/UserInfoService.java @@ -10,6 +10,8 @@ import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Root; import javax.servlet.http.HttpServletRequest; +import api.Result; +import api.StatusCode; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang3.RandomStringUtils; import org.springframework.amqp.rabbit.core.RabbitTemplate; @@ -250,6 +252,49 @@ public class UserInfoService { return Constants.TOKEN_PREFIX + jwtUtil.createJWT(userInfo.getId(), userInfo.getNickName(), Constants.TOKEN_ROLE_APP_USER, userInfo.getAvatar()); } + + + public String wechatloginOrRegister(UserInfo loginUserInfo) { + UserInfo userInfo = userInfoDao.findUserInfoByWxId(loginUserInfo.getWxId()); + if (null == userInfo) { + userInfo = loginUserInfo; + userInfo.setId(String.valueOf(idWorker.nextId())); + userInfo.setNickName("雀乐-" + NickNameUtil.getRandomNickName()); + Date curDate = new Date(); + userInfo.setJoinTime(curDate); + userInfo.setLastLoginTime(curDate); + userInfo.setStatus(UserStatusEnum.ENABLE.getStatus()); + userInfo.setAvatar(Constants.DEFAULT_USER_AVATAR); + userInfo.setThumbnail(Constants.DEFAULT_USER_THUMBNAIL); + userInfo.setSignature(Constants.DEFAULT_USER_SIGNATURE); + } else { + userInfo.setLastUseDeviceId(loginUserInfo.getLastUseDeviceId()); + userInfo.setLastUseDeviceBrand(loginUserInfo.getLastUseDeviceBrand()); + userInfo.setLastLoginIp(loginUserInfo.getLastLoginIp()); + } + + if (!UserStatusEnum.ENABLE.getStatus().equals(userInfo.getStatus())) { + return null; + } + userInfo.setLastLoginTime(new Date()); + userInfoDao.save(userInfo); + return Constants.TOKEN_PREFIX + + jwtUtil.createJWT(userInfo.getId(), userInfo.getNickName(), Constants.TOKEN_ROLE_APP_USER, userInfo.getAvatar()); + } + + public Result setPhoneNumber(String userid, String phoneNumber){ + UserInfo userbyId = userInfoDao.getById(userid); + if(userbyId == null) + return Result.failed(StatusCode.USER_INVALID_USER_ID); + userbyId.setMobile(phoneNumber); + + UserInfo save = userInfoDao.save(userbyId); + + if(save != null) + return Result.success(); + return Result.failed(); + } + public List orderByField(List idList) { return userInfoDao.orderByField(idList); } diff --git a/luoo_user/src/main/java/com/luoo/user/service/WeChatAouth2Service.java b/luoo_user/src/main/java/com/luoo/user/service/WeChatAouth2Service.java new file mode 100644 index 0000000..c70399c --- /dev/null +++ b/luoo_user/src/main/java/com/luoo/user/service/WeChatAouth2Service.java @@ -0,0 +1,188 @@ +package com.luoo.user.service; + +import api.Result; +import api.StatusCode; +import com.alibaba.fastjson.JSON; +import com.luoo.user.dto.response.Aouth2WeChatJSAPIResponse; +import com.luoo.user.dto.request.WeChatBindReq; +import com.luoo.user.dto.request.WeChatLoginReq; +import com.luoo.user.dto.request.WeChatShareReq; +import com.luoo.user.dto.response.Aouth2WeChatErrorResponse; +import com.luoo.user.dto.response.Aouth2WeChatTokenResponse; +import com.luoo.user.pojo.UserInfo; +import constants.Constants; +import dto.UserLoginDto; +import lombok.extern.slf4j.Slf4j; +import org.apache.http.Consts; +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.DefaultHttpClient; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Service; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.UnsupportedEncodingException; +import java.net.URI; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.concurrent.TimeUnit; + +/** + * @author Revers. + * @date 2024/03/25 15:00 + **/ + +//{"openid":"o5arS6kwYVmRexHdhJgjir9toPdw", +// "access_token":"78_VdzZCtUYiA83SDirkIUdir0SqWeb_LyUeX7A9CrszGUvHOd5AmWHFnWM1hEDoXpGQGOhzdQ1g478Z1ZDV8cmpyiKhSKgfJVSiEaVHWTO1vo", +// "expires_in":7200,"refresh_token":"78__Vyvij1j4NBfeZ_KTVDaE6XvKnU2J-OtprQC8EfkSTJszq08E1lwI_Wa_dj48b30j4RxQdugRpu8RJ-6l42bJWl5XyoUuk-8J4UyF1nlfDg", +// "scope":"snsapi_base"} +@Slf4j +@Service +public class WeChatAouth2Service { + + @Value("${oauth2.weixin.appid}") + private String appid; + + @Value("${oauth2.weixin.secret}") + private String secret; + + @Autowired + private UserInfoService userInfoService; + + @Autowired + private RedisTemplate redisTemplate; + + public Result getShareToken(WeChatShareReq weChatShareReq){ + Aouth2WeChatTokenResponse accessToken = getAccessToken(weChatShareReq.getCode()); + if(accessToken == null || accessToken.getAccess_token() == null ) + return Result.failed("分享微信失败"); + + Aouth2WeChatJSAPIResponse aouth2WeChatJSAPIResponse = getjsapi(accessToken.getAccess_token()); + if(aouth2WeChatJSAPIResponse == null || aouth2WeChatJSAPIResponse.getTicket() == null) + return Result.failed("分享微信失败"); + + return Result.success(aouth2WeChatJSAPIResponse.getTicket()); + } + + public Result loginOrRegister(WeChatLoginReq loginReq,String ipAddress){ + + Aouth2WeChatTokenResponse accessToken = getAccessToken(loginReq.getCode()); + if(accessToken == null || accessToken.getAccess_token() == null){ + return Result.failed(StatusCode.WECHAT_lOGIN_FAILED); + } + + UserInfo loginUserInfo = new UserInfo(); + loginUserInfo.setWxId(accessToken.getOpenid()); + loginUserInfo.setLastUseDeviceId(loginReq.getDeviceId()); + loginUserInfo.setLastUseDeviceBrand(loginReq.getDeviceBrand()); + loginUserInfo.setLastLoginIp(ipAddress); + String token = userInfoService.wechatloginOrRegister(loginUserInfo); + return Result.success(token); + } + + + public Result bindPhoneNumber(UserLoginDto user, WeChatBindReq weChatBindReq){ + return userInfoService.setPhoneNumber(user.getUserId(),weChatBindReq.getMobile()); + } + + public Aouth2WeChatTokenResponse getAccessToken(String code){ + StringBuffer url = new StringBuffer(); + url.append(Constants.HTTPS_API_WEIXIN_QQ_COM_SNS_OAUTH2_ACCESS_TOKEN); + url.append("?appid=").append(urlEncode(appid)); + url.append("&secret=").append(urlEncode(secret)); + url.append("&code=").append(urlEncode(code)); + url.append("&grant_type=authorization_code"); + String string = new String(get(url.toString())); + + log.info("refreshAccessToken:" + string); + Aouth2WeChatTokenResponse aouth2WeChatTokenResponse = JSON.parseObject(string, Aouth2WeChatTokenResponse.class); + saveTokenToRedis(aouth2WeChatTokenResponse); + + return aouth2WeChatTokenResponse; + } + + public Aouth2WeChatTokenResponse refreshAccessToken(String refresh_token) { + StringBuffer url = new StringBuffer(); + url.append(Constants.HTTPS_API_WEIXIN_QQ_COM_SNS_OAUTH2_REFRESH_TOKEN); + url.append("?appid=").append(urlEncode(appid)); + url.append("&grant_type=").append("refresh_token"); + url.append("&refresh_token=").append(urlEncode(refresh_token)); + String string = new String(get(url.toString())); + + log.info("refreshAccessToken:" + string); + Aouth2WeChatTokenResponse aouth2WeChatTokenResponse = JSON.parseObject(string, Aouth2WeChatTokenResponse.class); + saveTokenToRedis(aouth2WeChatTokenResponse); + + return aouth2WeChatTokenResponse; + } + + public Boolean isValidAccessToken(String access_token,String openid) { + StringBuffer url = new StringBuffer(); + url.append(Constants.HTTPS_API_WEIXIN_QQ_COM_SNS_AUTH); + url.append("?access_token=").append(urlEncode(access_token)); + url.append("&openid=").append(urlEncode(openid)); + String string = new String(get(url.toString())); + log.info("validAccessToken:" + string); + Aouth2WeChatErrorResponse dto = JSON.parseObject(string, Aouth2WeChatErrorResponse.class); + if(dto.getErrcode() == 0) + return true; + return false; + } + + private Aouth2WeChatJSAPIResponse getjsapi(String access_token) { + StringBuffer url = new StringBuffer(); + url.append(Constants.HTTPS_API_WEIXIN_QQ_JSAPI); + url.append("?access_token=").append(urlEncode(access_token)); + url.append("&type=").append("jsapi"); + String string = new String(get(url.toString())); + log.info("jsapi:" + string); + Aouth2WeChatJSAPIResponse aouth2WeChatJSAPIResponse = JSON.parseObject(string, Aouth2WeChatJSAPIResponse.class); + return aouth2WeChatJSAPIResponse; + } + + + private static String urlEncode(String str) { + String result = null; + try { + result = URLEncoder.encode(str, Consts.UTF_8.name()); + } catch (UnsupportedEncodingException e) { + } + return result; + } + + + private static byte[] get(String url) { + byte[] res = null; + try { + HttpResponse response = new DefaultHttpClient().execute(new HttpGet(URI.create(url))); + if (response.getStatusLine().getStatusCode() == 200) { + HttpEntity entity = response.getEntity(); + + BufferedReader reader = new BufferedReader(new InputStreamReader(entity.getContent(), "UTF-8")); + StringBuilder sb = new StringBuilder(); + + for (String temp = reader.readLine(); temp != null; temp = reader.readLine()) { + sb.append(temp); + } + res = sb.toString().trim().getBytes(StandardCharsets.UTF_8); + + } + } catch (IOException e) { + } + return res; + } + + private void saveTokenToRedis(Aouth2WeChatTokenResponse aouth2WeChatTokenResponse){ + if(aouth2WeChatTokenResponse != null && aouth2WeChatTokenResponse.getOpenid() != null ){ + if(aouth2WeChatTokenResponse.getAccess_token() != null) + redisTemplate.opsForValue().set(Constants.REDIS_KEY_WECHAT_TOKEN + aouth2WeChatTokenResponse.getOpenid(),aouth2WeChatTokenResponse.getAccess_token(),2, TimeUnit.HOURS); + if(aouth2WeChatTokenResponse.getRefresh_token() != null) + redisTemplate.opsForValue().set(Constants.REDIS_KEY_WECHAT_REFRESH_TOKEN + aouth2WeChatTokenResponse.getOpenid(),aouth2WeChatTokenResponse.getRefresh_token(),30, TimeUnit.DAYS); + } + } +} diff --git a/luoo_user/src/main/resources/bootstrap.yml b/luoo_user/src/main/resources/bootstrap.yml index e3a9d1d..2c580b1 100644 --- a/luoo_user/src/main/resources/bootstrap.yml +++ b/luoo_user/src/main/resources/bootstrap.yml @@ -5,4 +5,9 @@ spring: profile: dev label: master uri: http://116.62.145.60:12000 -# uri: http://127.0.0.1:12000 \ No newline at end of file +# uri: http://127.0.0.1:12000 + +oauth2: + weixin: + appid: wx667f580d1605650b + secret: 4bb42de98cb422d733ac7d50e7cade3b \ No newline at end of file