27 Commits

Author SHA1 Message Date
cce6bfd896 oauth-重写-Oauth异常 2025-10-12 00:45:41 +08:00
a8aa415858 oauth-重写-简化合并组件 2025-10-12 00:45:40 +08:00
5ad43fd617 oauth-重写-ticket和密码重置 2025-10-12 00:45:39 +08:00
600c439a43 oauth-重写-/oauth/token和第三方登录 2025-10-12 00:45:38 +08:00
a762e35456 oauth-重写-引入jjwt和删除h5登录相关 2025-10-12 00:45:36 +08:00
69dde07fc9 oauth-重写 2025-10-12 00:45:14 +08:00
1112abb97c 独联体公会列表 2025-10-12 00:38:12 +08:00
904859d496 角标配置入口-财富等级限制生效范围 2025-10-12 00:35:08 +08:00
8aab7d11fb h5登录取消限制 2025-10-12 00:35:07 +08:00
b29dcfedd7 web-api 2025-10-12 00:35:06 +08:00
4985074412 清理废弃功能代码-后台-补回GoldCoin下属的userService 2025-10-12 00:35:04 +08:00
29f7e88f17 mq-调低每个消费组底层消费线程池的核心线程数 2025-10-12 00:35:04 +08:00
2b90820c07 清理废弃功能代码-oauth 2025-10-12 00:35:03 +08:00
7e6f17acad pom-清理阿里云七牛云sdk 2025-10-12 00:35:02 +08:00
3176cf50b6 gitignore-将idea vscode等开发工具配置排除 2025-10-12 00:35:00 +08:00
aec5d86119 清理废弃功能代码-Anchor 2025-10-12 00:34:59 +08:00
daefb00c15 清理废弃功能代码 2025-10-12 00:34:43 +08:00
7fc64a660c pom-清理flow-team模块-清理admin模块里对flow-team的引用 2025-10-12 00:31:02 +08:00
a607e1098d pom-清理es依赖 2025-10-12 00:31:01 +08:00
2b385a036b 替换Jedis-sa-token集成Redisson 2025-10-12 00:30:58 +08:00
ec0d3e1c8c pom-清理flow-team模块-后台登录退出不再返回thymeleaf模板 2025-10-12 00:30:57 +08:00
64f9daf8e4 替换Jedis-使用RedissonClient的StringCodec编解码器 2025-10-12 00:30:56 +08:00
7b1d87cd75 替换Jedis-使用RedissonClient实现JedisService 2025-10-12 00:30:50 +08:00
1d180cf261 替换Jedis-删除JedisService废弃api 2025-10-12 00:30:49 +08:00
7790cab3b0 pom-清理flow-team模块-清理业务入侵 2025-10-12 00:30:46 +08:00
9452221e87 替换Jedis-整合JedisLockService到JedisService 2025-10-12 00:30:43 +08:00
2e3b031780 pom-清理flow-team模块 2025-10-12 00:30:38 +08:00
90 changed files with 3186 additions and 2246 deletions

View File

@@ -1,51 +0,0 @@
package com.accompany.admin.service.activity;
import com.accompany.business.model.activity.PageActivity;
import com.accompany.business.service.activity.PageActivityService;
import com.accompany.business.vo.activities.PageActivityVO;
import com.accompany.core.util.BeanUtils;
import com.baomidou.mybatisplus.core.metadata.IPage;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Calendar;
/**
* 页面静态活动配置 service
*
* @author linuxea
* @date 2019/10/9 11:01
*/
@Service
@Slf4j
@Transactional(rollbackFor = Exception.class)
public class StaticActivityPageService {
private final PageActivityService pageActivityService;
@Autowired
public StaticActivityPageService(PageActivityService pageActivityService) {
this.pageActivityService = pageActivityService;
}
public IPage<PageActivity> queryList(Integer pageNumber, Integer pageSize) {
return pageActivityService.pageList(null, pageNumber, pageSize);
}
public void save(PageActivityVO pageActivityVo) {
PageActivity pageActivity = BeanUtils.map(pageActivityVo, PageActivity.class);
pageActivity.setCreateTime(Calendar.getInstance().getTime());
pageActivity.setUpdateTime(Calendar.getInstance().getTime());
pageActivityService.save(pageActivity);
}
public void deleteByActivityId(Integer id) {
pageActivityService.removeById(id);
}
public Boolean existByCode(String actCode) {
return pageActivityService.countByCode(actCode) > 0;
}
}

View File

@@ -1,69 +0,0 @@
package com.accompany.admin.controller.activity;
import com.accompany.admin.controller.BaseController;
import com.accompany.admin.service.activity.StaticActivityPageService;
import com.accompany.business.model.activity.PageActivity;
import com.accompany.business.vo.activities.PageActivityVO;
import com.accompany.common.result.BusiResult;
import com.accompany.common.status.BusiStatus;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
/**
* 静态活动配置
*
* @author linuxea
* @date 2019/10/9
*/
@RestController
@RequestMapping("/admin/act/static")
public class StaticActivityPageController extends BaseController {
@Autowired
private StaticActivityPageService staticActivityPageService;
/**
* 分页列表
*
* @param pageNumber 页码
* @param pageSize 页长
*/
@GetMapping("/list")
public void queryList(@RequestParam("pageNumber") Integer pageNumber, @RequestParam("pageSize") Integer pageSize) {
IPage<PageActivity> pageInfo = this.staticActivityPageService.queryList(pageNumber, pageSize);
JSONObject jsonObject = new JSONObject();
jsonObject.put("total", pageInfo.getTotal());
jsonObject.put("rows", pageInfo.getRecords());
writeJson(jsonObject.toJSONString());
}
/**
* 创建
* @param pageActivity
* @return
*/
@PostMapping(value = "/save")
public BusiResult saveOperationAct(PageActivityVO pageActivity) {
Boolean exist = staticActivityPageService.existByCode(pageActivity.getCode());
if (exist) {
return new BusiResult(BusiStatus.CODE_DUPLICATE);
}
staticActivityPageService.save(pageActivity);
return new BusiResult<>(BusiStatus.SUCCESS);
}
/**
* 删除
*
* @param id 静态活动 id
* @return 结果
*/
@PostMapping(value = "/delete")
public BusiResult delOperationAct(Integer id) {
staticActivityPageService.deleteByActivityId(id);
return new BusiResult(BusiStatus.SUCCESS);
}
}

View File

@@ -73,33 +73,34 @@ public class RoomBoomAwardRecordAdminController extends BaseController {
//1 创建IPage分页对象,设置分页参数
IPage<RoomBoomSign> page=new Page<>(params.getPageNo(),params.getPageSize());
Long uid = null;
Long roomUid = null;
LambdaQueryWrapper<RoomBoomSign> wrapper = new LambdaQueryWrapper<>();
if (params.getRoomErbanNo() != null){
Users erbanNo = usersService.getUserByErbanNo(params.getRoomErbanNo());
if (erbanNo == null) {
throw new AdminServiceException(BusiStatus.SERVERERROR, "房间id不存在");
}
roomUid = erbanNo.getUid();
wrapper.eq(RoomBoomSign::getRoomUid,erbanNo.getUid());
}
if (params.getErbanNo() != null){
Users erbanNo = usersService.getUserByErbanNo(params.getErbanNo());
if (erbanNo == null) {
throw new AdminServiceException(BusiStatus.SERVERERROR, "触发者id不存在");
}
uid = erbanNo.getUid();
wrapper.eq(RoomBoomSign::getUid,erbanNo.getUid());
}
Date startTime = null, endTime = null;
if (StringUtils.isNotEmpty(params.getStartTime())) {
startTime = DateUtil.parseDateTime(params.getStartTime());
if (params.getLevel() != null){
wrapper.eq(RoomBoomSign::getLevel,params.getLevel());
}
if (StringUtils.isNotEmpty(params.getEndTime())) {
endTime = DateUtil.parseDateTime(params.getEndTime());
if (StringUtils.isNotEmpty(params.getStartTime())){
wrapper.ge(RoomBoomSign::getCreateTime, (Date)DateUtil.parseDateTime(params.getStartTime()));
}
if (StringUtils.isNotEmpty(params.getEndTime())){
wrapper.le(RoomBoomSign::getCreateTime, (Date)DateUtil.parseDateTime(params.getEndTime()));
}
wrapper.eq(RoomBoomSign::getStatus,2);
wrapper.orderByDesc(RoomBoomSign::getId);
//2 执行分页查询
IPage<RoomBoomSign> boomSignIPage = roomBoomSignService.list(page, uid, roomUid, params.getLevel(),
startTime, endTime, params.getPartitionId());
IPage<RoomBoomSign> boomSignIPage = roomBoomSignService.page(page, wrapper);
Page<RoomBoomSignVO> resultVo = new Page<>();
resultVo.setTotal(boomSignIPage.getTotal());
List<RoomBoomSignVO> roomBoomSignVOS = new ArrayList<>();

View File

@@ -76,6 +76,4 @@ public interface UsersMapper {
List<Users> listUid(@Param("start") Integer start, @Param("len") Integer len);
Integer recharegeCount(@Param("uid") Long uid);
}

View File

@@ -57,16 +57,6 @@ public class AccountService extends ServiceImpl<AccountMapper, Account> {
return accounts.get(0);
}
public Account getAccount(String phone) {
Account account = getAccountByPhone(phone);
if (account == null) {
if (!CommonUtil.checkValidPhone(phone)) {
account = getAccountByErBanNo(Long.valueOf(phone));
}
}
return account;
}
public Account getAccountByPhone(String phone) {
QueryWrapper<Account> wrapper = new QueryWrapper<>();
wrapper.lambda().eq(Account::getPhone, phone);
@@ -145,7 +135,7 @@ public class AccountService extends ServiceImpl<AccountMapper, Account> {
return accountList;
}
public String refreshAndGetNetEaseToken(Account account) throws Exception {
public String refreshAndGetNetEaseToken(Account account) {
TokenRet tokenRet = netEaseService.refreshToken(account.getUid().toString());
//未注册自动注册云信
if (tokenRet != null && StrUtil.isNotEmpty(tokenRet.getDesc()) &&tokenRet.getDesc().contains("not register")) {
@@ -154,20 +144,6 @@ public class AccountService extends ServiceImpl<AccountMapper, Account> {
return (String) tokenRet.getInfo().get("token");
}
public Account refreshNetEaseTokne(Long uid) throws Exception {
Account account = getById(uid);
String uidStr = String.valueOf(account.getUid());
TokenRet tokenRet = netEaseService.refreshToken(uidStr);
String token = (String) tokenRet.getInfo().get("token");
Account accountUpdate = new Account();
accountUpdate.setUid(account.getUid());
accountUpdate.setNeteaseToken(token);
accountUpdate.setErbanNo(account.getErbanNo());
accountUpdate.setDeviceId(account.getDeviceId());
save(accountUpdate);
return accountUpdate;
}
/**
* 初次设置
*

View File

@@ -22,6 +22,7 @@ import com.google.gson.reflect.TypeToken;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
@@ -522,9 +523,4 @@ public class UsersBaseService extends BaseService {
deleteUserRelateCache(uid.toString());
}
public Integer rechargeUserCount(Long uid) {
return usersMapper.recharegeCount(uid);
}
}

View File

@@ -1,116 +0,0 @@
package com.accompany.core.util;
import com.accompany.common.config.WebSecurityConfig;
import com.accompany.core.model.Account;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* Created by yuanyi on 2019/2/21.
*/
@Component
public class JwtUtils {
/**
* 用户登录成功后生成Jwt
* 使用Hs256算法 私匙使用用户密码
* @param ttlMillis jwt过期时间
* @return
*/
public String createJWT(Long ttlMillis, Long uid) {
//指定签名的时候使用的签名算法也就是header那部分jjwt已经将这部分内容封装好了。
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
//生成JWT的时间
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
//创建payload的私有声明根据特定的业务需要添加如果要拿这个做验证一般是需要和jwt的接收方提前沟通好验证方式的
Map<String, Object> claims = new HashMap<String, Object>();
claims.put("uid", uid);
//生成签名的时候使用的秘钥secret,这个方法本地封装了的一般可以从本地配置文件中读取切记这个秘钥不能外露哦。它就是你服务端的私钥在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。
// String key = PropertyUtil.getProperty("jwtH5Key");
String key = WebSecurityConfig.jwtWebKey;
//生成签发人,这里以平台号为签发人
String subject = uid.toString();
//下面就是在为payload添加各种标准声明和私有声明了
//这里其实就是new一个JwtBuilder设置jwt的body
JwtBuilder builder = Jwts.builder()
//如果有私有声明一定要先设置这个自己创建的私有的声明这个是给builder的claim赋值一旦写在标准的声明赋值之后就是覆盖了那些标准的声明的
.setClaims(claims)
//设置jti(JWT ID)是JWT的唯一标识根据业务需要这个可以设置为一个不重复的值主要用来作为一次性token,从而回避重放攻击。
.setId(UUID.randomUUID().toString())
//iat: jwt的签发时间
.setIssuedAt(now)
//代表这个JWT的主体即它的所有人这个是一个json格式的字符串可以存放什么useridroldid之类的作为什么用户的唯一标志。
.setSubject(subject)
//设置签名使用的签名算法和签名使用的秘钥
.signWith(signatureAlgorithm, key);
if (ttlMillis >= 0) {
long expMillis = nowMillis + ttlMillis;
Date exp = new Date(expMillis);
//设置过期时间
builder.setExpiration(exp);
}
return builder.compact();
}
/**
* Token的解密
* @param token 加密后的token
* @return
*/
public Claims parseJWT(String token) {
//签名秘钥,和生成的签名的秘钥一模一样
String key = WebSecurityConfig.jwtWebKey;
//得到DefaultJwtParser
Claims claims = Jwts.parser()
//设置签名的秘钥
.setSigningKey(key)
//设置需要解析的jwt
.parseClaimsJws(token).getBody();
return claims;
}
/**
* 校验token
* 在这里可以使用官方的校验我这里校验的是token中携带的密码于数据库一致的话就校验通过
* @param token
* @return
*/
public Boolean isVerify(String token, Account account) {
//签名秘钥,和生成的签名的秘钥一模一样
String key = WebSecurityConfig.jwtWebKey;
//得到DefaultJwtParser
Claims claims = Jwts.parser()
//设置签名的秘钥
.setSigningKey(key)
//设置需要解析的jwt
.parseClaimsJws(token).getBody();
if (claims.get("password").equals(account.getPassword())) {
return true;
}
return false;
}
}

View File

@@ -1082,8 +1082,4 @@
/* SHARDINGSPHERE_HINT: WRITE_ROUTE_ONLY=true */
select uid, partition_id partitionId from users order by uid asc limit #{start},#{len}
</select>
<select id="recharegeCount" resultType="java.lang.Integer">
select ifnull(count(1),0) from recharge_user where uid = #{uid}
</select>
</mapper>

View File

@@ -640,7 +640,7 @@ public enum BusiStatus {
DEVICE_ERROR(500, "设备为空"),
REGISTER_NETEASE_FAIL(500, "注册云信IM账号失败"),
UPDATE_NETEASE_ROOM_FAIL(500, "云信聊天室信息更新失败"),
@@ -988,7 +988,6 @@ public enum BusiStatus {
ROOM_DAY_DIAMOND_REWARD_DATE_CHECK(500, "TODAY NOT ALLOW RECEIVE"),
GUILD_USD_OPT_LIMIT(500, "该交易类型已达到本周交易次数上限"),
GUILD_MEMBER_REMOVE_LIMIT(500, "仅在每个月1号、2号、3号才可以移除主播"),
H5_RECHARGE_USER_NOT_OPEN(1404, "Your account has not enabled this function yet, so it cannot be used."),
;
private final int value;

View File

@@ -1,68 +0,0 @@
package com.accompany.common.utils;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.List;
@Component
public class JsonUtil {
@Autowired
private ObjectMapper objectMapper;
private static ObjectMapper objectMapperInner;
@PostConstruct
private void setObjectMapperInner() {
objectMapperInner = objectMapper;
}
public static <T> T parseToClass(String jsonString, Class<T> tClass) {
try {
return objectMapperInner.readValue(jsonString, tClass);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public static String parseToString(Object o) {
try {
return objectMapperInner.writeValueAsString(o);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public static boolean isJsonObject(String json) {
try {
return objectMapperInner.readTree(json).isObject();
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
public static boolean isJsonArray(String json) {
try {
return objectMapperInner.readTree(json).isArray();
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
public static <T> List<T> parseArray(String json, Class<T> tClass) {
try {
return objectMapperInner.readValue(json, objectMapperInner.getTypeFactory()
.constructCollectionType(List.class, tClass));
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}

View File

@@ -1,14 +0,0 @@
package com.accompany.common.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Created by yuanyi on 2019/2/23.
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface H5Authorization {
}

View File

@@ -1,9 +1,8 @@
package com.accompany.core.util;
import com.accompany.core.base.SpringContextHolder;
import com.accompany.core.exception.ServiceException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import lombok.extern.slf4j.Slf4j;
import org.apache.logging.log4j.util.Strings;
@@ -17,12 +16,7 @@ import java.io.IOException;
public class JsonUtil {
private static ObjectMapper getMapper() {
ObjectMapper bean = new ObjectMapper();
//反序列化忽略多余属性
bean.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
//如果是空对象的时候,不抛异常
bean.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
return bean;
return SpringContextHolder.getBean(ObjectMapper.class);
}
public static <T> T parseToClass(String jsonString, Class<T> tClass) {

View File

@@ -5,7 +5,6 @@ import com.accompany.common.constant.Constant;
import com.accompany.common.constant.SmsConstant;
import com.accompany.common.device.DeviceInfo;
import com.accompany.common.redis.RedisKey;
import com.accompany.common.result.BusiResult;
import com.accompany.common.status.BusiStatus;
import com.accompany.common.utils.RandomUtil;
import com.accompany.common.utils.StringUtils;

View File

@@ -115,12 +115,10 @@ public interface RoomBoomConstant {
* 土耳其普通100%+lucky5%+Bravo2%
* 英语区普通100%+lucky2%+Bravo2%
* 英语2区普通100%+lucky5%+Bravo2%
* 独联体普通礼物100%+幸运礼物4%
* @return
*/
BigDecimal NORMAL_GIFT_BOOM_RATE = BigDecimal.valueOf(1.0D);
BigDecimal SUPER_GIFT_BOOM_RATE_AR = BigDecimal.valueOf(0.05D);
BigDecimal SUPER_GIFT_BOOM_RATE_SO = BigDecimal.valueOf(0.04D);
BigDecimal SUPER_GIFT_BOOM_RATE_EN = BigDecimal.valueOf(0.02D);
BigDecimal BRAVO_GIFT_BOOM_RATE_EN = BigDecimal.valueOf(0.02D);
@@ -129,8 +127,6 @@ public interface RoomBoomConstant {
LUCKY((type, partition) -> {
if (PartitionEnum.ENGLISH.getId() == partition || PartitionEnum.CHINESE.getId() == partition) {
return SUPER_GIFT_BOOM_RATE_EN;
} else if (PartitionEnum.SOVIET.getId() == partition) {
return SUPER_GIFT_BOOM_RATE_SO;
}
return SUPER_GIFT_BOOM_RATE_AR;
}),

View File

@@ -64,7 +64,7 @@ public enum GuildWithdrawAccountTypeEnum {
SWIFT_CODE("swiftcode", PartitionEnum.ENGLISH2.getId(), List.of(CountryEnum.NG),
List.of(GuildWithdrawAccountFieldEnum.COUNTRY, GuildWithdrawAccountFieldEnum.ACCOUNT_NO, GuildWithdrawAccountFieldEnum.ACCOUNT_NAME, GuildWithdrawAccountFieldEnum.SWIFT_CODE)),
USDT_SOVIET("USDT/Binance/Volet", PartitionEnum.SOVIET.getId(), List.of(CountryEnum.RU,CountryEnum.UZ,CountryEnum.BY,CountryEnum.UA,CountryEnum.KG,CountryEnum.TJ,CountryEnum.TM,
USDT_SOVIET("usdt", PartitionEnum.SOVIET.getId(), List.of(CountryEnum.RU,CountryEnum.UZ,CountryEnum.BY,CountryEnum.UA,CountryEnum.KG,CountryEnum.TJ,CountryEnum.TM,
CountryEnum.MD,CountryEnum.GE,CountryEnum.AM,CountryEnum.KZ,CountryEnum.Other),
List.of(GuildWithdrawAccountFieldEnum.COUNTRY, GuildWithdrawAccountFieldEnum.ACCOUNT)),

View File

@@ -6,7 +6,6 @@ import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
@Data
public class Lucky24GiftConfig {
@@ -64,22 +63,11 @@ public class Lucky24GiftConfig {
private BigDecimal storeRatio;
private Integer startHour;
private Integer endHour;
@Deprecated
private Integer inputThreshold;
@Deprecated
private Set<String> userRechargeLevels;
@Deprecated
private BigDecimal todayProductionRatio;
@Deprecated
private Long todayDiff;
@Deprecated
private Integer twoDayCountLimit;
private Integer dayCountLimit;
private List<Integer> timesJudgeArray;
// 今天avg(input)-三天内sum(input)-三天内productionRatio-drawMultiple
private TreeMap<BigDecimal, TreeMap<Long, TreeMap<BigDecimal, Integer>>> judgeConfig;
}
}

View File

@@ -1,37 +0,0 @@
package com.accompany.business.model.activity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
* @Author: yangming
* @Date: 2020/5/13 20:06
* @Description: 页面活动
**/
@Data
@TableName(value = "page_activity")
public class PageActivity implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(value = "id",type = IdType.AUTO)
private Long id;
private String code;
private String title;
private String secondTitle;
private String imgUrl;
private Date createTime;
private Date updateTime;
}

View File

@@ -1,26 +0,0 @@
package com.accompany.business.vo.activities;
import lombok.Data;
/**
* @author linuxea
* @date 2019/9/29 17:54
*/
@Data
public class PageActivityVO {
/** id 标识 */
private Long id;
/** 活动代码 */
private String code;
/** 活动页面标题 */
private String title;
/** 活动二级标题 */
private String secondTitle;
/** 页面活动图片地址 */
private String imgUrl;
}

View File

@@ -1,12 +0,0 @@
package com.accompany.business.mybatismapper.activity;
import com.accompany.business.model.activity.PageActivity;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* @Author: yangming
* @Date: 2020/5/13 20:14
* @Description: 页面活动
**/
public interface PageActivityMapper extends BaseMapper<PageActivity> {
}

View File

@@ -2,10 +2,8 @@ package com.accompany.business.mybatismapper.room;
import com.accompany.business.model.room.RoomBoomSign;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.apache.ibatis.annotations.Param;
import java.util.Date;
import java.util.List;
/**
@@ -36,8 +34,4 @@ public interface RoomBoomSignMapper extends BaseMapper<RoomBoomSign> {
* @return 开奖情况
*/
RoomBoomSign getOneByRecordIdAndLevel(@Param("recordId") Long recordId, @Param("level") Integer level);
IPage<RoomBoomSign> list(@Param("page") IPage<RoomBoomSign> page, @Param("roomUid") Long roomUid, @Param("uid") Long uid,
@Param("level") Integer level, @Param("startTime") Date startTime, @Param("endTime") Date endTime,
@Param("partitionId") Integer partitionId);
}

View File

@@ -1,49 +0,0 @@
package com.accompany.business.service.activity;
import com.accompany.business.model.activity.PageActivity;
import com.accompany.business.mybatismapper.activity.PageActivityMapper;
import com.accompany.core.util.StringUtils;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.util.List;
/**
* @Author: yangming
* @Date: 2020/5/13 20:16
* @Description: 页面活动配置
**/
@Service
public class PageActivityService extends ServiceImpl<PageActivityMapper,PageActivity> {
public PageActivity getByCode(String code){
QueryWrapper<PageActivity> wrapper = new QueryWrapper<>();
wrapper.lambda().eq(PageActivity::getCode,code)
.orderByDesc(PageActivity::getId);
List<PageActivity> pageActivities = list(wrapper);
if(CollectionUtils.isEmpty(pageActivities)){
return null;
}
return pageActivities.get(0);
}
public IPage<PageActivity> pageList(String searchKey, Integer page, Integer pageSize) {
QueryWrapper<PageActivity> wrapper = new QueryWrapper<>();
wrapper.lambda().like(StringUtils.isNoneBlank(searchKey),PageActivity::getCode,searchKey).or()
.like(StringUtils.isNoneBlank(searchKey),PageActivity::getTitle,searchKey).or()
.like(StringUtils.isNoneBlank(searchKey),PageActivity::getSecondTitle,searchKey)
.orderByDesc(PageActivity::getId);
IPage<PageActivity> iPage = new Page<>(page,pageSize);
return page(iPage,wrapper);
}
public long countByCode(String code) {
QueryWrapper<PageActivity> wrapper = new QueryWrapper<>();
wrapper.lambda().eq(PageActivity::getCode,code);
return count(wrapper);
}
}

View File

@@ -102,32 +102,22 @@ public class Lucky24GiftSendService {
private Map<Long, Lucky24Record> draw(Lucky24GiftConfig config, Lucky24GiftConfig partitionConfig, long senderUid, Integer partitionId,
Gift gift, int everyGiftNum, long everyoneGoldNum,
List<Long> receiverList, Room room, Date sendGiftTime, boolean extraSwitch) {
if (!extraSwitch
|| config.getBlackUidList().contains(senderUid)
|| userMetaService.getTimes(senderUid) <= (long) config.getPoolSize() * config.getNewUserPoolCount()){
if (!extraSwitch){
return draw(config, partitionConfig, senderUid, partitionId, gift, everyGiftNum, everyoneGoldNum, receiverList, room, sendGiftTime);
}
Lucky24GiftConfig.Lucky24ExtraPoolConfig extraPoolConfig = partitionConfig.getExtraPoolConfig();
Long extraLuckerUid = extraService.selectExtraLucker(extraPoolConfig, senderUid, partitionId, receiverList);
if (null == extraLuckerUid){
Long extraLucker = extraService.selectExtraLucker(extraPoolConfig, senderUid, partitionId, everyoneGoldNum, receiverList);
if (null == extraLucker){
return draw(config, partitionConfig, senderUid, partitionId, gift, everyGiftNum, everyoneGoldNum, receiverList, room, sendGiftTime);
}
Integer extraDrawMultiple = extraService.drawMultiple(extraPoolConfig, senderUid, partitionId);
if (null == extraDrawMultiple){
return draw(config, partitionConfig, senderUid, partitionId, gift, everyGiftNum, everyoneGoldNum, receiverList, room, sendGiftTime);
}
Lucky24Record extraRecord = extraService.buildRecord(config, partitionConfig, senderUid, partitionId, extraLuckerUid, gift, everyGiftNum, everyoneGoldNum, room, sendGiftTime, extraDrawMultiple);
if (null == extraRecord){
return draw(config, partitionConfig, senderUid, partitionId, gift, everyGiftNum, everyoneGoldNum, receiverList, room, sendGiftTime);
}
Lucky24Record extraRecord = extraService.randomExtraRecord(config, senderUid, partitionId, extraLucker, gift, everyGiftNum, everyoneGoldNum, room, sendGiftTime);
List<Long> receiverUidList = new ArrayList<>(receiverList);
receiverUidList.remove(extraLuckerUid);
receiverUidList.remove(extraLucker);
Map<Long, Lucky24Record> recordMap = draw(config, partitionConfig, senderUid, partitionId, gift, everyGiftNum, everyoneGoldNum, receiverUidList, room, sendGiftTime);
recordMap.put(extraLuckerUid, extraRecord);
recordMap.put(extraLucker, extraRecord);
return recordMap;
}

View File

@@ -2,9 +2,9 @@ package com.accompany.business.service.lucky;
import com.accompany.business.constant.Lucky24PoolTypeEnum;
import com.accompany.business.dto.lucky.Lucky24GiftConfig;
import com.accompany.business.dto.lucky.Lucky24Result;
import com.accompany.business.model.Gift;
import com.accompany.common.utils.DateTimeUtil;
import com.accompany.common.utils.RandomUtil;
import com.accompany.core.enumeration.PartitionEnum;
import com.accompany.core.model.Room;
import com.accompany.payment.service.UserRechargeLevelService;
@@ -14,14 +14,13 @@ import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RMap;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.ZonedDateTime;
import java.util.*;
import static com.accompany.business.service.lucky.Lucky24UserMetaService.*;
import java.util.Date;
import java.util.List;
import java.util.Map;
@Slf4j
@Service
@@ -30,6 +29,8 @@ public class Lucky24ExtraService {
@Autowired
private Lucky24ExtraStockService stockService;
@Autowired
private UserRechargeLevelService userRechargeLevelService;
@Autowired
private Lucky24UserMetaService userMetaService;
@Autowired
private Lucky24RobotMsgService robotMsgService;
@@ -37,193 +38,104 @@ public class Lucky24ExtraService {
private Lucky24SettlementService settlementService;
@Autowired
private Lucky24RecordService recordService;
@Autowired
private UserRechargeLevelService userRechargeLevelService;
public BigDecimal addStock(Integer partitionId, BigDecimal addScore) {
return stockService.addStock(partitionId, addScore);
}
public Long selectExtraLucker(Lucky24GiftConfig.Lucky24ExtraPoolConfig extraPoolConfig, long senderUid, Integer partitionId, List<Long> receiverList) {
public Long selectExtraLucker(Lucky24GiftConfig.Lucky24ExtraPoolConfig extraPoolConfig, long senderUid, Integer partitionId, long everyoneGoldNum, List<Long> receiverList) {
PartitionEnum partitionEnum = PartitionEnum.getByPartitionId(partitionId);
ZonedDateTime zdt = DateTimeUtil.getDateTimeByZoneId(partitionEnum.getZoneId());
if (zdt.getHour() < extraPoolConfig.getStartHour() || zdt.getHour() > extraPoolConfig.getEndHour()){
return null;
}
if (everyoneGoldNum < extraPoolConfig.getInputThreshold()){
return null;
}
String userRechargeLevel = userRechargeLevelService.getLevelByUid(senderUid);
if (!extraPoolConfig.getUserRechargeLevels().contains(userRechargeLevel)){
return null;
}
RMap<String, Number> userMetaMap = userMetaService.getUserMeta(senderUid);
Map<String, Number> userMetaSnapshot = userMetaMap.readAllMap();
long todayStartTimeLong = DateTimeUtil.getZonedTodayTime(partitionEnum.getZoneId());
String today = String.valueOf(todayStartTimeLong);
String todayInputKey = String.join("_", today, Lucky24UserMetaService.INPUT_KEY);
long todayInput = userMetaSnapshot.getOrDefault(todayInputKey, 0L).longValue();
String todayOutputKey = String.join("_", today, Lucky24UserMetaService.OUTPUT_KEY);
long todayOutput = userMetaSnapshot.getOrDefault(todayOutputKey, 0L).longValue();
String todayDayCountKey = String.join("_", today, Lucky24UserMetaService.EXTRA_POOL_COUNT);
int todayDayCount = userMetaSnapshot.getOrDefault(todayDayCountKey, 0).intValue();
if (todayDayCount >= extraPoolConfig.getDayCountLimit()){
BigDecimal todayProductionRatio = todayInput > 0L && todayOutput > 0L ? BigDecimal.valueOf(todayOutput).divide(BigDecimal.valueOf(todayInput), 4, RoundingMode.HALF_UP) : BigDecimal.ZERO;
if (todayProductionRatio.compareTo(extraPoolConfig.getTodayProductionRatio()) >= 0){
log.info("[lucky24] extra todayProductionRation fail uid {} partitionId {} todayInput {} todayOutput {} pr {} configPr {}",
senderUid, partitionId, todayInput, todayOutput, todayProductionRatio, extraPoolConfig.getTodayProductionRatio());
return null;
}
String todayTimesKey = String.join("_", today, TIMES_KEY);
long todayTimes = userMetaSnapshot.getOrDefault(todayTimesKey, 0L).longValue();
long todayDiff = todayInput - todayOutput;
if (todayDiff < extraPoolConfig.getTodayDiff()){
log.info("[lucky24] extra todayDiff fail uid {} partitionId {} todayInput {} todayOutput {} todayDiff {} configTodayDiff {}",
senderUid, partitionId, todayInput, todayOutput, todayDiff, extraPoolConfig.getTodayDiff());
return null;
}
List<Integer> timesJudgeList = extraPoolConfig.getTimesJudgeArray();
int maxJudgeTimes = timesJudgeList.get(timesJudgeList.size() - 1);
String yesterday = zdt.minusDays(1L).format(DateTimeUtil.dateFormatter);
String lastTwoDayKey = String.join("_", Lucky24UserMetaService.EXTRA_POOL_COUNT, yesterday);
int lastTwoDayCount = userMetaSnapshot.getOrDefault(lastTwoDayKey, 0).intValue();
if (lastTwoDayCount >= extraPoolConfig.getTwoDayCountLimit()){
return null;
}
Long luckyer = null;
long todayTimesBefore = todayTimes;
for (Long receiver: receiverList){
long todayTimesAfter = todayTimesBefore + 1;
if (todayTimesBefore < maxJudgeTimes){
for (Integer judgeTimes: timesJudgeList){
if (todayTimesBefore < judgeTimes && todayTimesAfter >= judgeTimes){
luckyer = receiver;
break;
} else if (judgeTimes == maxJudgeTimes && todayTimesAfter >= maxJudgeTimes){
long beforeMod = todayTimesBefore / maxJudgeTimes;
long afterMod = todayTimesAfter / maxJudgeTimes;
if (beforeMod < afterMod){
luckyer = receiver;
break;
}
}
}
if (null != luckyer){
break;
}
} else {
long beforeMod = todayTimesBefore / maxJudgeTimes;
long afterMod = todayTimesAfter / maxJudgeTimes;
if (beforeMod < afterMod){
luckyer = receiver;
break;
}
int expectedValue = lastTwoDayCount + 1;
int result = userMetaMap.compute(lastTwoDayKey, (key, currentVal) -> {
if (currentVal == null || currentVal.intValue() == lastTwoDayCount) {
return expectedValue;
}
todayTimesBefore = todayTimesAfter;
}
if (null == luckyer){
return currentVal; // 如果当前值不等于期望值,则返回原值,不做修改
}).intValue();
if (result != expectedValue){
log.error("[lucky24] extra cas failure uid {} partitionId {} lastTwoDayKey {} expectedValue {} result {}",
senderUid, partitionId, lastTwoDayCount, expectedValue, result);
return null;
}
// cas 当天剩余额外次数
int afterValue = userMetaMap.addAndGet(todayDayCountKey, 1).intValue();
if (afterValue > extraPoolConfig.getDayCountLimit()){
log.error("[lucky24] extra addAndGet fail uid {} partitionId {} todayDayKey {} afterValue {}", senderUid, partitionId, todayDayCount, afterValue);
return null;
}
String todayKey = String.join("_", Lucky24UserMetaService.EXTRA_POOL_COUNT, zdt.format(DateTimeUtil.dateFormatter));
int todayAfter = userMetaMap.addAndGet(todayKey, 1).intValue();
log.info("[lucky24] extra addAndGet true uid {} partitionId {} todayDayKey {} afterValue {}", senderUid, partitionId, todayDayCount, afterValue);
log.error("[lucky24] extra cas success uid {} partitionId {} lastTwoDayKey {} expectedValue {} result {} todayKey {} todayAfter {} lucker {}",
senderUid, partitionId, lastTwoDayKey, expectedValue, result, todayKey, todayAfter, receiverList.get(0));
return luckyer;
return receiverList.get(0);
}
public Integer drawMultiple(Lucky24GiftConfig.Lucky24ExtraPoolConfig extraPoolConfig, long senderUid, Integer partitionId) {
public Lucky24Record randomExtraRecord(Lucky24GiftConfig config, long senderUid, Integer partitionId, Long receiverUid,
Gift gift, int giftNum, long everyoneGoldNum, Room room, Date sendGiftTime) {
int random = RandomUtil.randomByRange(0, 10);
int drawMultiple = random < 5 ? 1000: random < 9 ? 500: 250;
Map<String, Number> userMetaMapSnapshot = userMetaService.getUserMeta(senderUid).readAllMap();
PartitionEnum partitionEnum = PartitionEnum.getByPartitionId(partitionId);
long todayStartTimeLong = DateTimeUtil.getZonedTodayTime(partitionEnum.getZoneId());
String today = String.valueOf(todayStartTimeLong);
String todayTimesKey = String.join("_", today, TIMES_KEY);
long todayTimes = userMetaMapSnapshot.getOrDefault(todayTimesKey, 0L).longValue();
String todayInputKey = String.join("_", today, INPUT_KEY);
long todayInput = userMetaMapSnapshot.getOrDefault(todayInputKey, 0L).longValue();
String todayOutputKey = String.join("_", today, INPUT_KEY);
long todayOutput = userMetaMapSnapshot.getOrDefault(todayOutputKey, 0L).longValue();
// 第一层
// 今天avg(input)
BigDecimal todayAvgInput = todayInput <= 0L? BigDecimal.ZERO:
BigDecimal.valueOf(todayInput).divide(BigDecimal.valueOf(todayTimes), 4, RoundingMode.HALF_UP);
// floor返回小于或等于 key 的最大键值对
Map.Entry<BigDecimal, TreeMap<Long, TreeMap<BigDecimal, Integer>>> inputJudgeEntry = extraPoolConfig.getJudgeConfig().floorEntry(todayAvgInput);
if (null == inputJudgeEntry){
return null;
}
TreeMap<Long, TreeMap<BigDecimal, Integer>> inputJudgeMap = inputJudgeEntry.getValue();
if (CollectionUtils.isEmpty(inputJudgeMap)){
return null;
}
log.info("[lucky24] extra randomExtra first uid {} partitionId {} todayInput {} todayTimes {} todayAvgInput {} ceilingKey {}",
senderUid, partitionId, todayInput, todayTimes, todayAvgInput.toPlainString(), inputJudgeEntry.getKey());
// 第二层
// 三天内sum(input)
String twoDayAgoInputKey = String.join("_", INPUT_KEY, TWO_DAY_AGO);
long twoDayAgoInput = userMetaMapSnapshot.getOrDefault(twoDayAgoInputKey, 0L).longValue();
long threeDayAgoInput = todayInput + twoDayAgoInput;
// ceiling返回大于或等于 key 的最小键值对
Map.Entry<Long, TreeMap<BigDecimal, Integer>> threeDayAgoInputEntry = inputJudgeMap.ceilingEntry(threeDayAgoInput);
if (null == threeDayAgoInputEntry){
return null;
}
TreeMap<BigDecimal, Integer> threeDayAgoInputMap = threeDayAgoInputEntry.getValue();
if (CollectionUtils.isEmpty(threeDayAgoInputMap)){
return null;
}
log.info("[lucky24] extra randomExtra second uid {} partitionId {} todayInput {} twoDayAgoInput {} threeDayAgoInput {} floorKey {}",
senderUid, partitionId, todayInput, twoDayAgoInput, threeDayAgoInput, threeDayAgoInputEntry.getKey());
// 第三层
// 三天内productionRation
String twoDayAgoOutputKey = String.join("_", OUTPUT_KEY, TWO_DAY_AGO);
long twoDayAgoOutput = userMetaMapSnapshot.getOrDefault(twoDayAgoOutputKey, 0L).longValue();
long threeDayAgoOutput = todayOutput + twoDayAgoOutput;
BigDecimal threeDayProductionRatio = threeDayAgoOutput <= 0L || threeDayAgoInput <= 0 ? BigDecimal.ZERO:
BigDecimal.valueOf(threeDayAgoOutput).divide(BigDecimal.valueOf(threeDayAgoInput), 4, RoundingMode.HALF_UP);
// floor返回小于或等于 key 的最大键值对
Map.Entry<BigDecimal, Integer> threeDayProductionRatioEntry = threeDayAgoInputMap.ceilingEntry(threeDayProductionRatio);
Integer drawMultiple = null == threeDayProductionRatioEntry || null == threeDayProductionRatioEntry.getValue()?
null: threeDayProductionRatioEntry.getValue();
log.info("[lucky24] extra randomExtra uid {} partitionId {} todayOutput {} twoDayAgoOutput {} threeDayAgoOutput {} threeDayAgoInput {} productionRation {} floorKey {} result {}",
senderUid, partitionId, todayOutput, twoDayAgoOutput, threeDayAgoOutput, threeDayAgoInput, threeDayProductionRatio.toPlainString(), threeDayAgoInputEntry.getKey(), drawMultiple);
return drawMultiple;
}
public Lucky24Record buildRecord(Lucky24GiftConfig config, Lucky24GiftConfig partitionConfig, long senderUid, Integer partitionId, Long receiverUid,
Gift gift, int giftNum, long everyoneGoldNum, Room room, Date sendGiftTime, Integer drawMultiple){
long afterMultiple = drawMultiple;
// 额外平台库存
long preWinGoldNum = afterMultiple * everyoneGoldNum;
BigDecimal preWinGoldNumDecimal = BigDecimal.valueOf(preWinGoldNum);
Lucky24StockResultVo stockResultVo = judgeStock(partitionId, preWinGoldNumDecimal, senderUid, receiverUid, gift, giftNum, everyoneGoldNum, room, sendGiftTime);
// 平台库存
BigDecimal preWinGoldNum = BigDecimal.valueOf(afterMultiple * everyoneGoldNum);
Lucky24StockResultVo stockResultVo = judgeStock(partitionId, preWinGoldNum, senderUid, receiverUid, gift, giftNum, everyoneGoldNum, room, sendGiftTime);
if (!stockResultVo.isSuccess()){
return null;
afterMultiple = 0L;
}
// 个人库存
String userRechargeLevel = userRechargeLevelService.getLevelByUid(senderUid);
stockResultVo = userMetaService.judgePersonalStock(partitionConfig, senderUid, receiverUid, everyoneGoldNum, userRechargeLevel, afterMultiple, preWinGoldNum);
afterMultiple = stockResultVo.getMultiple();
long winGoldNum = afterMultiple * everyoneGoldNum;
if (winGoldNum > 0L){
settlementService.sendReward(config, senderUid, room, gift, winGoldNum, afterMultiple);
}
userMetaService.updateUserMeta(senderUid, partitionId, everyoneGoldNum, winGoldNum);
userMetaService.updateExtraUserMeta(senderUid, partitionId, everyoneGoldNum, winGoldNum);
log.info("[lucky24] extra uid {} partitionId {} receiverUid {} drawMultiple {} preWinGoldNum {} afterMultiple {} winGoldNum {}",
senderUid, partitionId, receiverUid, drawMultiple, preWinGoldNum, afterMultiple, winGoldNum);
log.info("[lucky24] extra uid {} partitionId {} receiverUid {} random {} drawMultiple {} preWinGoldNum {} afterMultiple {} winGoldNum {}",
senderUid, partitionId, receiverUid, random, drawMultiple, preWinGoldNum, afterMultiple, winGoldNum);
return recordService.buildRecord(senderUid, partitionId, gift, giftNum, null != room ? room.getUid() : null,
return recordService.buildRecord(senderUid, partitionId, gift, giftNum, null != room? room.getUid(): null,
receiverUid, Lucky24PoolTypeEnum.EXTRA_POOL.getType(), null,
Boolean.FALSE, drawMultiple, afterMultiple, !stockResultVo.isSuccess()? stockResultVo: null, sendGiftTime);
}
@@ -246,5 +158,4 @@ public class Lucky24ExtraService {
resultVo.setPreWinGoldNum(winGoldNum);
return resultVo;
}
}

View File

@@ -17,18 +17,19 @@ import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
@Slf4j
@Service
public class Lucky24UserMetaService {
public static final String TIMES_KEY = "times";
private static final String TIMES_KEY = "times";
public static final String INPUT_KEY = "input";
public static final String OUTPUT_KEY = "output";
private static final String TODAY = "today";
public static final String TWO_DAY_AGO = "two_day_ago";
public static final String EXTRA_POOL_COUNT = "extra_pool_count";
@@ -151,15 +152,13 @@ public class Lucky24UserMetaService {
long todayStartTimeLong = DateTimeUtil.getZonedTodayTime(partitionEnum.getZoneId());
String today = String.valueOf(todayStartTimeLong);
String todayTimesKey = String.join("_", today, TIMES_KEY);
long todayTimes = userMetaMap.addAndGet(todayTimesKey, 1L).longValue();
String todayInputKey = String.join("_", today, INPUT_KEY);
long todayInput = userMetaMap.addAndGet(todayInputKey, input).longValue();
String todayOutputKey = String.join("_", today, OUTPUT_KEY);
long todayOutput = userMetaMap.addAndGet(todayOutputKey, output).longValue();
log.info("[Lucky24] updateExtraUserMeta uid {} times {} today {} todayTime {} todayInput {} todayOutput {}",
senderUid, times, todayStartTimeLong, todayTimes, todayInput, todayOutput);
log.info("[Lucky24] updateExtraUserMeta uid {} times {} today {} todayInput {} todayOutput {}",
senderUid, times, todayStartTimeLong, todayInput, todayOutput);
}
public void updateUserMeta(long senderUid, int partitionId, long input, long output) {
@@ -182,8 +181,6 @@ public class Lucky24UserMetaService {
long todayStartTimeLong = DateTimeUtil.getZonedTodayTime(partitionEnum.getZoneId());
String today = String.valueOf(todayStartTimeLong);
String todayTimesKey = String.join("_", today, TIMES_KEY);
long todayTimes = userMetaMap.addAndGet(todayTimesKey, 1L).longValue();
String todayInputKey = String.join("_", today, INPUT_KEY);
long todayInput = userMetaMap.addAndGet(todayInputKey, input).longValue();
String todayOutputKey = String.join("_", today, OUTPUT_KEY);
@@ -192,76 +189,28 @@ public class Lucky24UserMetaService {
long userMetaToday = userMetaMap.computeIfAbsent(TODAY, k->todayStartTimeLong).longValue();
if (userMetaToday < todayStartTimeLong
&& userMetaMap.replace(TODAY, userMetaToday, todayStartTimeLong)){
Map<String, Number> userMetaMapSnapshot = userMetaMap.readAllMap();
// 清理昨天
String oldToday = String.valueOf(userMetaToday);
String oldTodayTimesKey = String.join("_", oldToday, TIMES_KEY);
String oldTodayInputKey = String.join("_", oldToday, INPUT_KEY);
String oldTodayOutputKey = String.join("_", oldToday, OUTPUT_KEY);
String oldTodayExtraPoolKey = String.join("_", oldToday, EXTRA_POOL_COUNT);
userMetaMap.fastRemove(oldTodayInputKey, oldTodayOutputKey);
int oneDayAgoDiff = 24 * 60 * 60 * 1000;
// 清理额外线计数器
String todayExtraPoolKey = String.join("_", EXTRA_POOL_COUNT, today);
String yesterday = DateTimeUtil.getDateTimeByZoneId(partitionEnum.getZoneId()).minusDays(1L).format(DateTimeUtil.dateFormatter);
String yesterdayExtraPoolKey = String.join("_", EXTRA_POOL_COUNT, yesterday);
if (userMetaToday >= todayStartTimeLong - 2 * oneDayAgoDiff){
String oldDayInputKey = String.join("_", INPUT_KEY, oldToday);
long oldTodayInput = userMetaMapSnapshot.getOrDefault(oldTodayInputKey, 0L).longValue();
String oldDayOutputKey = String.join("_", OUTPUT_KEY, oldToday);
long oldTodayOutput = userMetaMapSnapshot.getOrDefault(oldTodayOutputKey, 0L).longValue();
// cache snapshot
userMetaMapSnapshot.putAll(Map.of(oldDayInputKey, oldTodayInput, oldDayOutputKey, oldTodayOutput));
Set<String> needDeleteKeySet = new HashSet<>();
for (String key : userMetaMapSnapshot.keySet()){
if ((key.startsWith(INPUT_KEY) && !key.equals(INPUT_KEY))
|| (key.startsWith(OUTPUT_KEY) && !key.equals(OUTPUT_KEY))){
if (key.endsWith(TWO_DAY_AGO)){
continue;
}
needDeleteKeySet.add(key);
}
String extraPoolKeyPattern = EXTRA_POOL_COUNT + "*";
Set<String> extraPoolKeySet = userMetaMap.keySet(extraPoolKeyPattern);
for (String extraPoolKey : extraPoolKeySet){
if (!extraPoolKey.equals(todayExtraPoolKey) && !extraPoolKey.equals(yesterdayExtraPoolKey)){
userMetaMap.fastRemove(extraPoolKey);
}
long twoDayAgoInput = 0L;
long twoDayAgoOutput = 0L;
for (int i = 1; i < 3; i++){
long dateStartTimeLong = todayStartTimeLong - i * oneDayAgoDiff;
String date = String.valueOf(dateStartTimeLong);
String dayInputKey = String.join("_", INPUT_KEY, date);
long dayInput = userMetaMapSnapshot.getOrDefault(dayInputKey, 0L).longValue();
needDeleteKeySet.remove(dayInputKey);
twoDayAgoInput += dayInput;
String dayOutputKey = String.join("_", OUTPUT_KEY, date);
long dayOutput = userMetaMapSnapshot.getOrDefault(dayOutputKey, 0L).longValue();
needDeleteKeySet.remove(dayOutputKey);
twoDayAgoOutput += dayOutput;
}
String twoDayAgoInputKey = String.join("_", INPUT_KEY, TWO_DAY_AGO);
String twoDayAgoOutputKey = String.join("_", OUTPUT_KEY, TWO_DAY_AGO);
// cache
userMetaMap.putAll(Map.of(oldDayInputKey, oldTodayInput, oldDayOutputKey, oldTodayOutput,
twoDayAgoInputKey, twoDayAgoInput, twoDayAgoOutputKey, twoDayAgoOutput));
// clear key
needDeleteKeySet.addAll(List.of(oldTodayInputKey, oldTodayOutputKey, oldTodayTimesKey, oldTodayExtraPoolKey));
userMetaMap.fastRemove(needDeleteKeySet.toArray(String[]::new));
}
}
log.info("[Lucky24] updateUserMeta uid {} times {} todayInput {} todayOutput {} today {} todayTimes {} todayIntput {} todayOutput {}",
senderUid, times, totalInput, totalOutput, todayStartTimeLong, todayTimes, todayInput, todayOutput);
log.info("[Lucky24] updateUserMeta uid {} times {} today {} todayInput {} todayOutput {}",
senderUid, times, todayStartTimeLong, todayInput, todayOutput);
}
public RMap<String, Number> getUserMeta(Long uid) {

View File

@@ -19,7 +19,7 @@ import com.accompany.business.service.user.UserBackpackService;
import com.accompany.business.util.redenvelope.OpenRedEnvelopeUtil;
import com.accompany.common.constant.Attach;
import com.accompany.common.constant.Constant;
import com.accompany.common.utils.JsonUtil;
import com.accompany.core.util.JsonUtil;
import com.accompany.core.base.SpringContextHolder;
import com.accompany.core.model.Room;
import lombok.extern.slf4j.Slf4j;

View File

@@ -16,7 +16,7 @@ import com.accompany.business.service.user.UsersService;
import com.accompany.common.constant.Attach;
import com.accompany.common.constant.Constant;
import com.accompany.common.utils.DateTimeUtil;
import com.accompany.common.utils.JsonUtil;
import com.accompany.core.util.JsonUtil;
import com.accompany.core.enumeration.I18nAlertEnum;
import com.accompany.core.model.Users;
import com.accompany.core.service.partition.PartitionInfoService;

View File

@@ -17,7 +17,7 @@ import com.accompany.common.constant.Attach;
import com.accompany.common.constant.Constant;
import com.accompany.common.status.BusiStatus;
import com.accompany.common.utils.DateTimeUtil;
import com.accompany.common.utils.JsonUtil;
import com.accompany.core.util.JsonUtil;
import com.accompany.core.enumeration.I18nAlertEnum;
import com.accompany.core.exception.ServiceException;
import com.accompany.core.model.Users;

View File

@@ -208,8 +208,8 @@ public class ResourceServiceImpl extends ServiceImpl<ResourceMapper, Resource> i
isSkip = Integer.parseInt(val1) > Integer.parseInt(val2);
}
} else if (OperateType.GE.name().equals(kind)) {
boolean needLimit = RuleCodeEnum.USER_LEVEL.name().equals(key) && limitUserLevel;
if (needLimit) {
limitUserLevel = RuleCodeEnum.USER_LEVEL.name().equals(key) && limitUserLevel;
if (!limitUserLevel) {
if (targetValue.toString().contains(StrUtil.DOT) && !targetValue.toString().startsWith(StrUtil.DELIM_START) && !targetValue.toString().startsWith(StrUtil.DELIM_END)){
isSkip = AppVersionUtil.compareVersion(val1, val2) < 0;
} else {

View File

@@ -4,6 +4,7 @@ import cn.hutool.core.date.DateUtil;
import com.accompany.business.constant.RoomBoomConstant;
import com.accompany.business.dto.room.RoomBoomPrizeMsgDTO;
import com.accompany.business.message.GiftMessage;
import com.accompany.business.message.SuperLuckyGiftDiamondIncomeMessage;
import com.accompany.business.model.room.RoomBoomLevel;
import com.accompany.business.model.room.RoomBoomLevelAward;
import com.accompany.business.model.room.RoomBoomSign;
@@ -26,6 +27,7 @@ import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Lists;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.*;
@@ -42,6 +44,7 @@ import org.springframework.util.CollectionUtils;
import java.math.BigDecimal;
import java.time.ZonedDateTime;
import java.util.*;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.Collectors;

View File

@@ -1,10 +1,8 @@
package com.accompany.business.service.room;
import com.accompany.business.model.room.RoomBoomSign;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import java.util.Date;
import java.util.List;
@@ -43,7 +41,5 @@ public interface RoomBoomSignService extends IService<RoomBoomSign> {
* @return 记录
*/
RoomBoomSign getOneByRecordIdAndLevel(Long recordId, Integer level);
IPage<RoomBoomSign> list(IPage<RoomBoomSign> page, Long roomUid, Long uid, Integer level, Date startTime, Date endTime, Integer partitionId);
}

View File

@@ -1,287 +1,2 @@
package com.accompany.business.service.room.impl;
import cn.hutool.core.date.StopWatch;
import cn.hutool.core.lang.Pair;
import com.accompany.business.constant.GenderEnum;
import com.accompany.business.dto.QueueDTO;
import com.accompany.business.dto.QueueListResponseDTO;
import com.accompany.business.dto.QueueValueDTO;
import com.accompany.business.enums.GenderArea;
import com.accompany.business.service.room.*;
import com.accompany.common.constant.Constant;
import com.accompany.common.netease.ErBanNetEaseService;
import com.accompany.common.redis.RedisKey;
import com.accompany.common.status.BusiStatus;
import com.accompany.core.exception.ServiceException;
import com.accompany.core.model.Room;
import com.accompany.core.util.JsonUtil;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@Slf4j
@Service
@Transactional(rollbackFor = Exception.class)
public class BlindDateMaxGiftValueServiceImpl implements BlindDateMaxGiftValueService {
@Autowired
private BlindDateRoundConnectionService blindDateRoundConnectionService;
@Autowired
private QueueService queueService;
@Autowired
private QueryRoomService queryRoomService;
@Autowired
private MicCharmService micCharmService;
@Autowired
private BlindDateCapService blindDateCapService;
@Autowired
private RoomService roomService;
@Autowired
private RoomGiftValueService roomGiftValueService;
@Autowired
private ErBanNetEaseService erBanNetEaseService;
@Autowired
private RedissonClient redissonClient;
@Override
public void trigger(Long roomUid) {
StopWatch stw = new StopWatch("timer");
stw.start();
log.info("进入了相亲送社触发器");
Room roomDTO = roomService.getRoomByUid(roomUid);
if (roomDTO == null || roomDTO.getRoomModeType() != Constant.RoomModeType.OPEN_BLIND_DATE_MODE) {
log.info("非相亲玩法");
return;
}
boolean locked = false;
RLock lock = redissonClient.getLock(RedisKey.blind_room_max_gift_val_trigger_lock.getKey(roomUid.toString()));
try {
locked = lock.tryLock(5, TimeUnit.SECONDS);
if (!locked){
throw new ServiceException(BusiStatus.SERVERBUSY);
}
long roomId = roomDTO.getRoomId();
// 发发起魅力值同步
roomGiftValueService.pushCurrentGiftValues2Client(roomUid);
QueueListResponseDTO queueList = queryRoomService.queueList(roomDTO.getRoomId());
List<QueueDTO> listDTO = queueList.getDesc().listDTO();
roomGiftValueService.upMicUpdateVipCache(roomDTO, listDTO);
log.info("相亲送社触发器" + JsonUtil.parseToString(listDTO));
List<Long> micUserIds = listDTO.stream().map(dto -> dto.getValueObject().getUid()).collect(Collectors.toList());
// 获取到vip席位所在的麦位
QueueDTO vipMic = listDTO.stream().filter(dto -> dto.getValueObject().getVipMic()).findFirst().orElse(null);
int sign = 0;
if (vipMic == null) sign = 0;
else if (vipMic.getValueObject().getGender() == (byte) 1) sign = 1;
else if (vipMic.getValueObject().getGender() == (byte) 2) sign = 2;
List<Pair<Long, Integer>> positionPair = micUserIds.stream()
.map(uid -> new Pair<>(uid, blindDateRoundConnectionService.position(roomId, uid)))
.collect(Collectors.toList());
log.info("用户与位置关系: " + JsonUtil.parseToString(positionPair));
List<Pair<Long, Integer>> boyArea = positionPair.stream().filter(pair -> GenderArea.boyArea.contains(pair.getValue()))
.collect(Collectors.toList());
if (!boyArea.isEmpty()) {
Pair<Long, Integer> maxPairBoy = boyArea.stream()
.max(Comparator.comparingLong(pair -> getGiftValue(roomUid, pair.getKey())))
.orElseThrow(() -> new ServiceException(BusiStatus.UNEXPECTEDLY_EMPTY));
if (sign == 1 && GenderArea.vipArea.contains(vipMic.getValueObject().getPosition().intValue())) {
dealVipArea(roomUid, vipMic, listDTO, roomId, maxPairBoy, roomDTO, GenderEnum.MALE);
} else {
updateHatByArea(listDTO, maxPairBoy, roomId, roomDTO, GenderEnum.MALE);
}
}
// ========================================================================================
List<Pair<Long, Integer>> girlArea = positionPair.stream().filter(pair -> GenderArea.girlArea.contains(pair.getValue()))
.collect(Collectors.toList());
if (!girlArea.isEmpty()) {
Pair<Long, Integer> maxPairGirl = girlArea.stream()
.max(Comparator.comparingLong(pair -> getGiftValue(roomUid, pair.getKey())))
.orElseThrow(() -> new ServiceException(BusiStatus.UNEXPECTEDLY_EMPTY));
if (sign == 2 && GenderArea.vipArea.contains(vipMic.getValueObject().getPosition().intValue())) {
dealVipArea(roomUid, vipMic, listDTO, roomId, maxPairGirl, roomDTO, GenderEnum.FEMALE);
} else {
updateHatByArea(listDTO, maxPairGirl, roomId, roomDTO, GenderEnum.FEMALE);
}
}
// 主持人区
listDTO.stream().filter(dto -> GenderArea.hostArea.contains(dto.getValueObject().getPosition().intValue()))
.forEach(dto -> {
QueueValueDTO queueValueDTO = dto.getValueObject();
// 再次检查
queueValueDTO.setVipMic(false);
log.info("主持人区当前用户" + queueValueDTO.getUid() + "是vip?" + queueValueDTO.getVipMic());
// 主持人区不参与抢帽子
queueValueDTO.setCapUrl(null);
dto.setRoomid(roomId);
dto.setValue(JsonUtil.parseToString(queueValueDTO));
queueService.upsert(dto);
});
// vip区防止重新上麦上面的条件不满足没有处理到
listDTO.stream().filter(dto -> GenderArea.vipArea.contains(dto.getValueObject().getPosition().intValue()))
.forEach(dto -> {
QueueValueDTO queueValueDTO = dto.getValueObject();
// 再次检查
long vipCharmVal = getGiftValue(roomUid, queueValueDTO.getUid());
queueValueDTO.setVipMic(roomGiftValueService.checkUserIsVip(queueValueDTO.getUid(), roomDTO));
log.info("主持人区当前用户" + queueValueDTO.getUid() + "是vip?" + queueValueDTO.getVipMic());
if (queueValueDTO.getGender() == GenderEnum.MALE.getValue() && boyArea.isEmpty() && vipCharmVal > 0) {
// 检查帽子
queueValueDTO.setCapUrl(blindDateCapService.getByCharmValueAndGender(vipCharmVal, GenderEnum.MALE).getPicUrl());
}
if (queueValueDTO.getGender() == GenderEnum.FEMALE.getValue() && girlArea.isEmpty() && vipCharmVal > 0) {
// 检查帽子
queueValueDTO.setCapUrl(blindDateCapService.getByCharmValueAndGender(vipCharmVal, GenderEnum.FEMALE).getPicUrl());
}
dto.setRoomid(roomId);
dto.setValue(JsonUtil.parseToString(queueValueDTO));
queueService.upsert(dto);
});
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
if (locked){
lock.unlock();
}
stw.stop();
log.info("相亲社触发器执行耗时" + stw.prettyPrint());
}
}
private void dealVipArea(long roomUid, QueueDTO vipMic, List<QueueDTO> listDTO, long roomId, Pair<Long, Integer> maxPair, Room roomDTO, GenderEnum genderEnum) {
long vipCharmVal = getGiftValue(roomUid, vipMic.getValueObject().getUid());
long maxGiftValue = getGiftValue(roomUid, maxPair.getKey());
List<Integer> area = null;
if (genderEnum.equals(GenderEnum.MALE)) area = GenderArea.boyArea;
else area = GenderArea.girlArea;
log.info("当前vip麦位用户性别[" + genderEnum.getDesc() + "]性别所在区最大魅力值[" + maxGiftValue + "]vip用户魅力值[" + vipCharmVal + "]");
if (vipCharmVal >= maxGiftValue) {
// 更新队列
List<Integer> finalArea = area;
listDTO.stream().filter(dto -> finalArea.contains(dto.getValueObject().getPosition().intValue())).forEach(dto -> {
QueueValueDTO queueValueDTO = dto.getValueObject();
if (queueValueDTO.getCapUrl() != null) {
// 再次检查
queueValueDTO.setVipMic(roomGiftValueService.checkUserIsVip(queueValueDTO.getUid(), roomDTO));
if (queueValueDTO.getVipMic())
log.info("触发器1用户" + queueValueDTO.getUid() + "在麦位" + queueValueDTO.getPosition() + "获得vip");
queueValueDTO.setCapUrl(null);
dto.setRoomid(roomId);
dto.setValue(JsonUtil.parseToString(queueValueDTO));
queueService.upsert(dto);
}
});
vipMic.getValueObject().setCapUrl(blindDateCapService.getByCharmValueAndGender(vipCharmVal, genderEnum).getPicUrl());
vipMic.setRoomid(roomId);
vipMic.setValue(JsonUtil.parseToString(vipMic.getValueObject()));
queueService.upsert(vipMic);
log.info("更新 " + vipMic.getValueObject().getUid() + "" + genderEnum.getDesc() + "神帽子");
} else {
vipMic.getValueObject().setCapUrl(null);
vipMic.getValueObject().setVipMic(roomGiftValueService.checkUserIsVip(vipMic.getValueObject().getUid(), roomDTO));
if (!vipMic.getValueObject().getVipMic()) {
// 移除队列
log.info("触发器2用户" + vipMic.getValueObject().getUid() + "vip麦位position" + vipMic.getValueObject().getPosition() + "变为false");
try {
erBanNetEaseService.queuePoll(roomDTO.getRoomId(), vipMic.getValueObject().getPosition().toString());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
vipMic.setRoomid(roomId);
vipMic.setValue(JsonUtil.parseToString(vipMic.getValueObject()));
queueService.upsert(vipMic);
log.info("更新 " + vipMic.getValueObject().getUid() + "" + genderEnum.getDesc() + "神帽子");
updateHatByArea(listDTO, maxPair, roomId, roomDTO, genderEnum);
}
}
@Override
public void clear(Long roomUid) {
log.info("清空 " + roomUid + " 的帽子");
Room roomDTO = roomService.getRoomByUid(roomUid);
long roomId = roomDTO.getRoomId();
QueueListResponseDTO queueList = queryRoomService.queueList(roomId);
queueList.getDesc().listDTO().forEach(dto -> {
dto.getValueObject().setCapUrl(null);
dto.setRoomid(roomId);
dto.setValue(JsonUtil.parseToString(dto.getValueObject()));
queueService.upsert(dto);
});
}
private long getGiftValue(long roomUserId, long userId) {
long result = micCharmService.getByRoomUserIdAndUserId(roomUserId, userId);
log.info(roomUserId + " 中用户 " + userId + " 的礼物魅力值为 " + result);
return result;
}
private void updateHatByArea(List<QueueDTO> listDTO, Pair<Long, Integer> maxPair, long roomId, Room roomDTO, GenderEnum genderEnum) {
List<Integer> area = null;
if (genderEnum.equals(GenderEnum.MALE)) area = GenderArea.boyArea;
else area = GenderArea.girlArea;
// 更新队列
List<Integer> finalArea = area;
listDTO.stream().filter(dto -> finalArea.contains(dto.getValueObject().getPosition().intValue()) && !dto.getValueObject().getPosition().equals(maxPair.getKey()))
.forEach(dto -> {
QueueValueDTO queueValueDTO = dto.getValueObject();
if (queueValueDTO.getCapUrl() != null) {
// 检查当前用户是否已经变为vip
queueValueDTO.setVipMic(roomGiftValueService.checkUserIsVip(queueValueDTO.getUid(), roomDTO));
if (queueValueDTO.getVipMic())
log.info("触发器3用户" + queueValueDTO.getUid() + "在麦位" + queueValueDTO.getPosition() + "获得vip");
queueValueDTO.setCapUrl(null);
dto.setRoomid(roomId);
dto.setValue(JsonUtil.parseToString(queueValueDTO));
queueService.upsert(dto);
}
});
listDTO.stream().filter(dto -> dto.getValueObject().getPosition().intValue() == maxPair.getValue()).findFirst().ifPresent(dto -> {
QueueValueDTO queueValueDTO = dto.getValueObject();
// 检查当前用户是否已经变为vip
queueValueDTO.setVipMic(roomGiftValueService.checkUserIsVip(queueValueDTO.getUid(), roomDTO));
if (queueValueDTO.getVipMic())
log.info("触发器4用户" + queueValueDTO.getUid() + "在麦位" + queueValueDTO.getPosition() + "获得vip和帽子");
queueValueDTO.setCapUrl(blindDateCapService.getByCharmValueAndGender(getGiftValue(roomDTO.getUid(), maxPair.getKey()), genderEnum).getPicUrl());
dto.setRoomid(roomId);
dto.setValue(JsonUtil.parseToString(queueValueDTO));
queueService.upsert(dto);
log.info("更新 " + dto.getValueObject().getUid() + "" + genderEnum.getDesc() + "神帽子");
});
}
}

View File

@@ -11,7 +11,7 @@ import com.accompany.common.config.SystemConfig;
import com.accompany.common.constant.Attach;
import com.accompany.common.constant.Constant;
import com.accompany.common.utils.BeanUtil;
import com.accompany.common.utils.JsonUtil;
import com.accompany.core.util.JsonUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

View File

@@ -1,569 +1,2 @@
package com.accompany.business.service.room.impl;
import cn.hutool.core.util.StrUtil;
import com.accompany.business.constant.GenderEnum;
import com.accompany.business.dto.*;
import com.accompany.business.dto.blindDate.*;
import com.accompany.business.enums.BlindDatePhaseStateEnum;
import com.accompany.business.enums.BlindDatePickType;
import com.accompany.business.enums.RoomMicPositionType;
import com.accompany.business.event.BlindDateNotifyEvent;
import com.accompany.business.message.BlindDateNotifyMessage;
import com.accompany.business.model.redis.RoomMic;
import com.accompany.business.model.room.BlindDateCap;
import com.accompany.business.model.room.BlindDateJoinHand;
import com.accompany.business.service.room.*;
import com.accompany.business.service.user.UsersService;
import com.accompany.business.vo.RoomNotifyVo;
import com.accompany.business.vo.room.BlindDataConfigVO;
import com.accompany.common.config.SystemConfig;
import com.accompany.common.constant.Attach;
import com.accompany.common.constant.Constant;
import com.accompany.common.netease.neteaseacc.result.RoomRet;
import com.accompany.common.redis.RedisKey;
import com.accompany.common.status.BusiStatus;
import com.accompany.common.utils.UUIDUtil;
import com.accompany.core.exception.ServiceException;
import com.accompany.core.model.Room;
import com.accompany.core.model.Users;
import com.accompany.core.service.common.JedisService;
import com.accompany.core.util.JsonUtil;
import com.alibaba.fastjson.JSON;
import com.google.gson.Gson;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.*;
import java.util.stream.Collectors;
@Service
@Transactional(rollbackFor = Exception.class)
public class BlindDateServiceImpl implements BlindDateService {
private final org.slf4j.Logger log = LoggerFactory.getLogger(BlindDateServiceImpl.class);
@Autowired
private BlindDateRoundConnectionService blindDateRoundConnectionService;
@Autowired
private UsersService userService;
@Autowired
private PushRoomService pushRoomService;
@Autowired
private BlindDateRoundService blindDateRoundService;
@Autowired
private QueryRoomService queryRoomService;
@Autowired
private QueueService queueService;
@Autowired
private BlindDateJoinHandService blindDateJoinHandService;
@Autowired
private MicCharmService micCharmService;
@Autowired
private BlindDateRoundHistoryService blindDateRoundHistoryService;
@Autowired
private RoomService roomService;
@Autowired
private BlindDateMaxGiftValueService blindDateMaxGiftValueService;
@Autowired
private BlindDateRoundConnectionSuccessService blindDateRoundConnectionSuccessService;
@Autowired
private RoomQueueMicroService roomQueueMicroService;
@Autowired
private RoomGiftValueService roomGiftValueService;
@Autowired
private JedisService jedisService;
@Autowired
private IBlindDateCapService iBlindDateCapService;
@Autowired
private IBlindDateJoinHandService iBlindDateJoinHandService;
@Autowired
private ApplicationContext applicationContext;
protected Gson gson = new Gson();
@Override
public List<BlindDatePickDTO> matchView(Long roundId) {
BlindDateRoundDTO blindDateRoundDTO = blindDateRoundService.getById(roundId);
QueueListResponseDTO queueList = queryRoomService.queueList(blindDateRoundDTO.getRoomId());
List<QueueDTO> listDTO = queueList.getDesc().listDTO().stream()
.filter(dto -> dto.getValueObject().getPosition() != RoomMicPositionType.TOAST_MASTE.getPosition())
.collect(Collectors.toList());
List<Long> onMicUserId = listDTO.stream()
.map(dto -> dto.getValueObject().getUid())
.collect(Collectors.toList());
List<BlindDatePickDTO> list = blindDateRoundConnectionService.listByRoundId(roundId).stream()
.filter(connection -> onMicUserId.contains(connection.getSelectUserId()) &&
onMicUserId.contains(connection.getBeSelectedUserId()))
.map(connection -> {
BlindDatePickDTO blindDatePickDTO = new BlindDatePickDTO();
blindDatePickDTO.setOneUserId(connection.getSelectUserId());
blindDatePickDTO.setAnotherUserId(connection.getBeSelectedUserId());
blindDatePickDTO.setPickType(BlindDatePickType.SINGLE);
return blindDatePickDTO;
})
.collect(Collectors.toCollection(LinkedList::new));
List<Long> oneUserId = list.stream()
.map(BlindDatePickDTO::getOneUserId)
.collect(Collectors.toList());
List<Long> notSelectUserId = onMicUserId.stream()
.filter(userId -> !oneUserId.contains(userId))
.collect(Collectors.toList());
log.info("Users on mic but not selected or being selected: {}", JsonUtil.parseToString(notSelectUserId));
List<BlindDatePickDTO> notSelect = notSelectUserId.stream()
.map(userId -> {
BlindDatePickDTO blindDatePickDTO = new BlindDatePickDTO();
blindDatePickDTO.setOneUserId(userId);
return blindDatePickDTO;
})
.collect(Collectors.toList());
list.addAll(notSelect);
for (BlindDatePickDTO outerBlindDatePickDTO : list) {
for (BlindDatePickDTO innerBlindDatePickDTO : list) {
if (outerBlindDatePickDTO != null && innerBlindDatePickDTO != null) {
if (innerBlindDatePickDTO.getAnotherUserId() != null && outerBlindDatePickDTO.getAnotherUserId() != null && outerBlindDatePickDTO.getAnotherUserId() != null && outerBlindDatePickDTO.getOneUserId() == innerBlindDatePickDTO.getAnotherUserId() &&
outerBlindDatePickDTO.getAnotherUserId().equals(innerBlindDatePickDTO.getOneUserId())) {
outerBlindDatePickDTO.setPickType(BlindDatePickType.BOTH);
}
}
}
}
list.sort((dto1, dto2) -> {
int pos1 = listDTO.stream()
.filter(dto -> dto.getValueObject().getUid().equals(dto1.getOneUserId()))
.findFirst()
.map(dto -> dto.getValueObject().getPosition())
.orElse(-1L).intValue();
int pos2 = listDTO.stream()
.filter(dto -> dto.getValueObject().getUid().equals(dto2.getOneUserId()))
.findFirst()
.map(dto -> dto.getValueObject().getPosition())
.orElse(-1L).intValue();
return Integer.compare(pos1, pos2);
});
List<BlindDatePickDTO> removeBothWay = new LinkedList<>();
LinkedList<BlindDatePickDTO> newLinkedList = new LinkedList<>(list);
while (!newLinkedList.isEmpty()) {
BlindDatePickDTO pop = newLinkedList.pop();
if (removeBothWay.stream().noneMatch(dto -> pop.getAnotherUserId() != null && dto.getAnotherUserId() != null && dto.getOneUserId() == pop.getAnotherUserId() &&
dto.getAnotherUserId().equals(pop.getOneUserId()))) {
removeBothWay.add(pop);
}
}
LinkedList<BlindDatePickDTO> removeBothWay2 = new LinkedList<>(removeBothWay);
List<BlindDatePickDTO> linkedList = new LinkedList<>();
while (!removeBothWay2.isEmpty()) {
BlindDatePickDTO pop = removeBothWay2.pop();
if (pop.getPickType() == BlindDatePickType.BOTH) {
BlindDatePickDTO first = new BlindDatePickDTO();
first.setOneUserId(pop.getOneUserId());
first.setAnotherUserId(pop.getAnotherUserId());
first.setPickType(BlindDatePickType.SINGLE);
BlindDatePickDTO two = new BlindDatePickDTO();
two.setOneUserId(pop.getAnotherUserId());
two.setAnotherUserId(pop.getOneUserId());
two.setPickType(BlindDatePickType.SINGLE);
linkedList.add(first);
linkedList.add(two);
}
linkedList.add(pop);
}
log.info("Matching information for round {}: {}", roundId, JsonUtil.parseToString(linkedList));
return linkedList;
}
@Override
public List<DatingNotifyInfoDTO> buildNotifyDTO(Long roundId) {
BlindDatePhaseStateEnum latestStateByRoundId = blindDateRoundHistoryService.latestStateByRoundId(roundId);
List<BlindDatePickDTO> pick = matchView(roundId);
BlindDateRoundDTO blindDateRoundDTO = blindDateRoundService.getById(roundId);
List<DatingNotifyInfoDTO> list = pick.stream()
.map(dto -> {
Users oneUser = userService.getUsersByUid(dto.getOneUserId());
DatingNotifyInfoDTO datingNotifyInfoDTO = new DatingNotifyInfoDTO();
datingNotifyInfoDTO.setHasHeart(dto.getPickType() == BlindDatePickType.BOTH);
Long another = dto.getAnotherUserId();
Users anotherUser = userService.getUsersByUid(another);
if (anotherUser != null) {
datingNotifyInfoDTO.setTargetUid(anotherUser.getUid());
datingNotifyInfoDTO.setTargetNickname(StrUtil.isNotEmpty(anotherUser.getNick()) ? anotherUser.getNick() : "");
Integer position = blindDateRoundConnectionService.position(blindDateRoundDTO.getRoomId(), anotherUser.getUid());
datingNotifyInfoDTO.setTargetPosition(position != null ? position : -2);
Byte gender = anotherUser.getGender();
datingNotifyInfoDTO.setTargetGender(gender != null ? gender : 3);
if (datingNotifyInfoDTO.isHasHeart()) {
String avatar = anotherUser.getAvatar();
datingNotifyInfoDTO.setTargetAvatar(StrUtil.isNotEmpty(avatar) ? avatar : "");
}
}
if (oneUser != null) {
datingNotifyInfoDTO.setUid(oneUser.getUid());
String oneUserNick = oneUser.getNick();
datingNotifyInfoDTO.setNickname(StrUtil.isNotEmpty(oneUserNick) ? oneUserNick : "");
Integer position1 = blindDateRoundConnectionService.position(blindDateRoundDTO.getRoomId(), oneUser.getUid());
datingNotifyInfoDTO.setPosition(position1 != null ? position1 : -2);
Byte gender1 = oneUser.getGender();
datingNotifyInfoDTO.setGender(gender1 != null ? gender1 : 3);
if (datingNotifyInfoDTO.isHasHeart()) {
String avatar = oneUser.getAvatar();
datingNotifyInfoDTO.setAvatar(StrUtil.isNotEmpty(avatar) ? avatar : "");
}
}
datingNotifyInfoDTO.setHasSelectUser(dto.getPickType() != null);
if (dto.getPickType() == BlindDatePickType.BOTH && latestStateByRoundId == BlindDatePhaseStateEnum.PUBLISH) {
Room roomDTO = roomService.getRoomByRoomId(blindDateRoundDTO.getRoomId());
Long oneUserMicCharmLong = micCharmService.getByRoomUserIdAndUserId(roomDTO.getUid(), dto.getOneUserId());
int oneUserMicCharm = 0;
if (oneUserMicCharmLong != null) {
oneUserMicCharm = oneUserMicCharmLong.intValue();
}
Long anotherUserMicCharmLong = micCharmService.getByRoomUserIdAndUserId(roomDTO.getUid(), dto.getAnotherUserId());
int anotherUserMicCharm = 0;
if (anotherUserMicCharmLong != null) {
anotherUserMicCharm = anotherUserMicCharmLong.intValue();
}
int sum = oneUserMicCharm + anotherUserMicCharm;
BlindDateJoinHandDTO blindDateJoinHandDTO = blindDateJoinHandService.getByCharmValue((long) sum);
log.info("Users [{}] in room [{}] successfully matched, charm value is {}, scene is {}", dto.getOneUserId(), dto.getAnotherUserId(), sum, blindDateJoinHandDTO != null ? blindDateJoinHandDTO.getId() : null);
if (datingNotifyInfoDTO.isHasHeart()) {
datingNotifyInfoDTO.setSvgaUrl(blindDateJoinHandDTO != null ? blindDateJoinHandDTO.getPicUrl() : "");
datingNotifyInfoDTO.setSvgaSecond(blindDateJoinHandDTO != null ? blindDateJoinHandDTO.getPicSecond() : null);
}
BlindDateRoundConnectionSuccessDTO blindDateRoundConnectionSuccessDTO = new BlindDateRoundConnectionSuccessDTO();
blindDateRoundConnectionSuccessDTO.setBlindDateRoundId(blindDateRoundDTO.getId());
blindDateRoundConnectionSuccessDTO.setOneUserId(dto.getOneUserId());
blindDateRoundConnectionSuccessDTO.setOneUserGiftValue(oneUserMicCharm);
blindDateRoundConnectionSuccessDTO.setAnotherUserId(dto.getAnotherUserId());
blindDateRoundConnectionSuccessDTO.setAnotherUserGiftValue(anotherUserMicCharm);
blindDateRoundConnectionSuccessDTO.setBlindDateJoinHandUrl(blindDateJoinHandDTO != null ? blindDateJoinHandDTO.getPicUrl() : "");
blindDateRoundConnectionSuccessDTO.setSvgName(blindDateJoinHandDTO != null ? blindDateJoinHandDTO.getTitle() : "");
blindDateRoundConnectionSuccessService.create(blindDateRoundConnectionSuccessDTO);
if (Constant.status.valid.equals(blindDateJoinHandDTO != null ? blindDateJoinHandDTO.getNotifySwitch() : null)) {
publishBlindDateNotifyMsg(roomDTO, datingNotifyInfoDTO.getNickname(), datingNotifyInfoDTO.getTargetNickname(), blindDateJoinHandDTO);
}
if (Constant.BlindRoomDefaultVal.SEND_HEADWEAR_SWITCH_OPEN.equals(blindDateJoinHandDTO != null ? blindDateJoinHandDTO.getSendHeadwearSwitch() : null)) {
roomGiftValueService.blindDateSendHeadwear(blindDateJoinHandDTO, dto.getOneUserId(), dto.getAnotherUserId());
}
datingNotifyInfoDTO = roomGiftValueService.checkAndExchange(datingNotifyInfoDTO);
}
return datingNotifyInfoDTO;
})
.collect(Collectors.toList());
log.info("Built notification information for round {}: {}", roundId, JsonUtil.parseToString(list));
return list;
}
private void publishBlindDateNotifyMsg(Room room, String oneUserNick, String anotherUserNick, BlindDateJoinHandDTO blindDateJoinHandDTO) {
BlindDateNotifyMessage message = new BlindDateNotifyMessage();
message.setMessId(UUIDUtil.get());
message.setMessTime(System.currentTimeMillis());
message.setOneUserNick(oneUserNick);
message.setAnotherUserNick(anotherUserNick);
message.setRoomUid(room.getUid());
message.setRoomTitle(room.getTitle());
message.setJoinHandLevel(blindDateJoinHandDTO != null ? blindDateJoinHandDTO.getLevel() : null);
message.setJoinHandName(blindDateJoinHandDTO != null ? blindDateJoinHandDTO.getTitle() : null);
message.setBackgroundUrl(blindDateJoinHandDTO != null ? blindDateJoinHandDTO.getNotifyBackgroundUrl() : null);
message.setSweetWords(blindDateJoinHandDTO != null ? blindDateJoinHandDTO.getSweetWords() : null);
try {
applicationContext.publishEvent(new BlindDateNotifyEvent(message));
} catch (Exception e) {
log.error("publishBlindDateNotifyMsg error", e);
}
}
@Override
public void publish(Long roundId) {
notifyRound(roundId);
}
private void notifyRound(long roundId) {
BlindDateRoundDTO blindDateRoundDTO = blindDateRoundService.getById(roundId);
List<DatingNotifyInfoDTO> notifyDTOs = buildNotifyDTO(roundId);
if (notifyDTOs.isEmpty()) {
log.info("No one participated in this blind date, not sending messages to the client.");
return;
}
Attach attach = new Attach();
SendRoomMsgTemplate sendRoomMsgTemplate = new SendRoomMsgTemplate();
sendRoomMsgTemplate.setFromUserId(Long.parseLong(SystemConfig.systemMessageUid));
sendRoomMsgTemplate.setToRoomId(blindDateRoundDTO.getRoomId());
attach.setData(new ListWrapperDTO<>(notifyDTOs));
attach.setFirst(Constant.DefineProtocol.CUSTOM_MSG_DATING);
attach.setSecond(Constant.DefineProtocol.CUSTOM_MSG_SUB_DATING_PUBLISH_RESULT);
sendRoomMsgTemplate.setAttach(JsonUtil.parseToString(attach));
pushRoomService.sendRoomMsg(sendRoomMsgTemplate);
}
@Override
public void cleanQueue(Long roundId) {
// 队列还原
BlindDateRoundDTO blindDateRoundDTO = blindDateRoundService.getById(roundId);
long roomId = blindDateRoundDTO.getRoomId();
QueueListResponseDTO queueList = queryRoomService.queueList(roomId);
List<QueueDTO> listDTO = queueList.getDesc().listDTO();
for (QueueDTO dto : listDTO) {
QueueValueDTO valueObject = dto.getValueObject();
valueObject.setCapUrl(null);
valueObject.setHasSelectUser(null);
valueObject.setSelectMicPosition(null);
dto.setRoomid(roomId);
dto.setValue(JsonUtil.parseToString(valueObject));
queueService.upsert(dto);
log.info("清空" + roomId + "的相亲坑位信息");
}
}
@Override
public void buildQueue(Long roundId) {
List<BlindDatePickDTO> matchView = matchView(roundId);
BlindDateRoundDTO blindDateRoundDTO = blindDateRoundService.getById(roundId);
QueueListResponseDTO queueList = queryRoomService.queueList(blindDateRoundDTO.getRoomId());
List<QueueDTO> listDTO = queueList.getDesc().listDTO();
for (QueueDTO dto : listDTO) {
QueueValueDTO valueObject = dto.getValueObject();
BlindDatePickDTO myMatchView = matchView.stream()
.filter(match -> match.getOneUserId() == valueObject.getUid() &&
(match.getPickType() == BlindDatePickType.SINGLE || match.getPickType() == BlindDatePickType.BOTH))
.findFirst().orElse(null);
if (myMatchView != null) {
valueObject.setSelectMicPosition(blindDateRoundConnectionService.position(blindDateRoundDTO.getRoomId(), myMatchView.getAnotherUserId()).toString());
valueObject.setHasSelectUser(true);
} else {
valueObject.setSelectMicPosition(null);
valueObject.setHasSelectUser(false);
}
dto.setValue(JsonUtil.parseToString(valueObject));
dto.setRoomid(blindDateRoundDTO.getRoomId());
queueService.upsert(dto);
}
}
@Override
public void disConnect(Long roundId, Long userId, Long position) {
BlindDateRoundDTO blindDateRoundDTO = blindDateRoundService.getById(roundId);
long roomId = blindDateRoundDTO.getRoomId();
log.info(userId + " " + position + "" + roundId + " 下麦了");
// Delete information related to the user's selections
blindDateRoundConnectionService.deleteBySelectUserId(roundId, userId);
// Delete information related to the user being selected
blindDateRoundConnectionService.deleteByBeSelectUserId(roundId, userId);
// Update the queue
buildQueue(roundId);
// Trigger the calculation of the highest hat value
blindDateMaxGiftValueService.trigger(roomService.getRoomByRoomId(roomId).getUid());
}
@Override
public void updateBlindDateInfo(Long roomUid, boolean isOpen) {
Room roomByUserId = roomService.getRoomByUid(roomUid);
int posStatus = 1;
BlindDateRoundDTO latestByRoomId = null;
if (isOpen) {
Integer[] modeTypeList = {Constant.RoomModeType.NORMAL_MODE, Constant.RoomModeType.CLOSE_PK_MODE, Constant.RoomModeType.OPEN_MICRO_MODE, Constant.RoomModeType.CLOSE_MICRO_MODE};
if (!Arrays.asList(modeTypeList).contains(roomByUserId.getRoomModeType())) {
throw new ServiceException(BusiStatus.THE_CURRENT_ROOM_MODE_CANNOT_ENABLE_BLIND_DATES);
}
// 创建新的一轮相亲
BlindDateRoundDTO blindDateRoundDTO = new BlindDateRoundDTO();
blindDateRoundDTO.setRoomId(roomByUserId.getRoomId());
blindDateRoundService.create(blindDateRoundDTO);
latestByRoomId = blindDateRoundService.latestByRoomId(roomByUserId.getRoomId());
BlindDatePhaseStateEnum currentRoomState = blindDateRoundService.currentRoomState(roomByUserId.getRoomId());
// 新增阶段轮次记录
BlindDateRoundHistoryDTO blindDateRoundHistoryDTO = new BlindDateRoundHistoryDTO();
blindDateRoundHistoryDTO.setBlindDateRoundId(latestByRoomId.getId());
blindDateRoundHistoryDTO.setState(currentRoomState);
blindDateRoundHistoryDTO.setRoomId(roomByUserId.getRoomId());
blindDateRoundHistoryService.create(blindDateRoundHistoryDTO);
roomByUserId.setBlindDateState(currentRoomState.getCode());
roomByUserId.setRoomModeType(Constant.RoomModeType.OPEN_BLIND_DATE_MODE);
// 打开礼物魅力值
if (!roomByUserId.getShowGiftValue()) {
roomByUserId.setShowGiftValue(true);
}
// 发房间公屏
if (BlindDatePhaseStateEnum.COMMUNICATING.getTip() != null) {
SendRoomMsgTemplate sendRoomMsgTemplate = new SendRoomMsgTemplate();
sendRoomMsgTemplate.setFromUserId(Long.parseLong(SystemConfig.systemMessageUid));
sendRoomMsgTemplate.setToRoomId(roomByUserId.getRoomId());
sendRoomMsgTemplate.setAttach(BlindDatePhaseStateEnum.COMMUNICATING.getTip());
pushRoomService.sendTip(sendRoomMsgTemplate);
}
// 清空魅力值
try {
roomGiftValueService.cleanGiftValueCache(roomUid);
} catch (Exception e) {
throw new RuntimeException(e);
}
} else {
latestByRoomId = blindDateRoundService.latestByRoomId(roomByUserId.getRoomId());
roomByUserId.setBlindDateState(BlindDatePhaseStateEnum.CLOSED.getCode());
roomByUserId.setRoomModeType(Constant.RoomModeType.NORMAL_MODE);
}
// 更新数据库房间信息
roomService.updateDbAndCacheRoomInfo(roomByUserId);
if (roomByUserId.getBlindDateVipUid() != 0L) {
roomByUserId.setBlindDateVipUid(0L);
roomService.updateBlindDateVipUid(0L, roomByUserId);
Room endRoom = roomService.getRoomByUid(roomUid);
RoomNotifyVo roomNotifyVo = new RoomNotifyVo();
roomNotifyVo.setRoomInfo(gson.toJson(endRoom));
roomNotifyVo.setType(Constant.RoomMic.ROOM_NOTIFY_TYPE_ROOM);
try {
roomService.updateNetEaseRoomInfo(endRoom, gson.toJson(roomNotifyVo));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
if (!isOpen) {
roomGiftValueService.cleanRoomRoundSendGiftVal(roomByUserId.getUid(), latestByRoomId != null ? latestByRoomId.getId() : null);
}
//更新麦序信息到缓存
List<RoomMic> micInfo = roomQueueMicroService.queueBatchUpdate(roomByUserId, posStatus);
RoomNotifyVo roomNotifyVo = new RoomNotifyVo();
roomNotifyVo.setRoomInfo(gson.toJson(roomByUserId));
roomNotifyVo.setMicInfo(gson.toJson(micInfo));
roomNotifyVo.setType(Constant.RoomMic.ROOM_NOTIFY_TYPE_ROOM_MIC);
//通知云信更新聊天室信息
RoomRet roomRet = null;
try {
roomRet = roomService.updateNetEaseRoomInfo(roomByUserId, JSON.toJSONString(roomNotifyVo));
} catch (Exception e) {
throw new RuntimeException(e);
}
if (roomRet.getCode() == 200) {
// 清除队列中有关相亲的信息
if (latestByRoomId != null) {
cleanQueue(latestByRoomId.getId());
}
roomGiftValueService.pushCurrentGiftValues2Client(roomUid);
// 清除房间排麦队列
jedisService.hdel(RedisKey.room_mic_queue.getKey(), Long.toString(roomUid));
}
}
@Override
public BlindDataConfigVO getBlindDataConfig() {
BlindDataConfigVO blindDataConfigVO = new BlindDataConfigVO();
List<BlindDateCap> capList = iBlindDateCapService.list();
List<BlindDateCap> femaleCapList = new ArrayList<>();
List<BlindDateCap> maleCapList = new ArrayList<>();
if (capList != null && !capList.isEmpty()) {
for (BlindDateCap cap : capList) {
if (cap.getGender() == GenderEnum.MALE) {
maleCapList.add(cap);
} else {
femaleCapList.add(cap);
}
}
}
List<BlindDateJoinHand> joinHandList = iBlindDateJoinHandService.listBlindDateJoinHandWithOutDefault();
blindDataConfigVO.setFemaleCapList(femaleCapList);
blindDataConfigVO.setMaleCapList(maleCapList);
blindDataConfigVO.setJoinHandList(joinHandList);
return blindDataConfigVO;
}
@Override
public void testAA() {
List<BlindDatePickDTO> list = new ArrayList<>();
for (int i = 1; i <= 10; i++) {
BlindDatePickDTO blindDatePickDTO = new BlindDatePickDTO();
blindDatePickDTO.setOneUserId((long) i);
if (!(i == 5 || i == 6)) blindDatePickDTO.setAnotherUserId((long) (i + 10));
// 默认单选
blindDatePickDTO.setPickType(BlindDatePickType.SINGLE);
list.add(blindDatePickDTO);
}
for (int i = 11; i <= 20; i++) {
BlindDatePickDTO blindDatePickDTO = new BlindDatePickDTO();
blindDatePickDTO.setOneUserId((long) i);
blindDatePickDTO.setAnotherUserId((long) (i - 10));
// 默认单选
blindDatePickDTO.setPickType(BlindDatePickType.SINGLE);
list.add(blindDatePickDTO);
}
// 2.查看是否有双选
for (BlindDatePickDTO outerBlindDatePickDTO : list) {
for (BlindDatePickDTO innerBlindDatePickDTO : list) {
if (innerBlindDatePickDTO.getAnotherUserId() != null && outerBlindDatePickDTO.getAnotherUserId() != null && Objects.equals(outerBlindDatePickDTO.getOneUserId(), innerBlindDatePickDTO.getAnotherUserId()) &&
Objects.equals(outerBlindDatePickDTO.getAnotherUserId(), innerBlindDatePickDTO.getOneUserId())) {
outerBlindDatePickDTO.setPickType(BlindDatePickType.BOTH);
}
}
}
// 4.去除双向选择
LinkedList<BlindDatePickDTO> newLinkedList = new LinkedList<>(list);
List<BlindDatePickDTO> removeBothWay = new ArrayList<>();
while (!newLinkedList.isEmpty()) {
BlindDatePickDTO pop = newLinkedList.pop();
if (removeBothWay.stream().noneMatch(it -> it.getAnotherUserId() != null && pop.getAnotherUserId() != null && Objects.equals(it.getOneUserId(), pop.getAnotherUserId()) && Objects.equals(it.getAnotherUserId(), pop.getOneUserId()))) {
removeBothWay.add(pop);
}
}
//5.添加多余信息
LinkedList<BlindDatePickDTO> removeBothWay2 = new LinkedList<>(removeBothWay);
List<BlindDatePickDTO> linkedList = new ArrayList<>();
while (!removeBothWay2.isEmpty()) {
BlindDatePickDTO pop = removeBothWay2.pop();
if (pop.getPickType() == BlindDatePickType.BOTH) {
// 添加两条互相选择的实体
BlindDatePickDTO first = new BlindDatePickDTO();
first.setOneUserId(pop.getOneUserId());
first.setAnotherUserId(pop.getAnotherUserId());
first.setPickType(BlindDatePickType.SINGLE);
BlindDatePickDTO two = new BlindDatePickDTO();
two.setOneUserId(pop.getAnotherUserId());
two.setAnotherUserId(pop.getOneUserId());
two.setPickType(BlindDatePickType.SINGLE);
linkedList.add(first);
linkedList.add(two);
}
linkedList.add(pop);
}
log.info("配对信息" + JsonUtil.parseToString(linkedList));
}
}

View File

@@ -23,7 +23,6 @@ import com.accompany.core.model.Users;
import com.accompany.core.util.I18NMessageSourceUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.nacos.common.utils.CollectionUtils;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RList;
@@ -383,11 +382,6 @@ public class RoomBoomSignServiceImpl extends ServiceImpl<RoomBoomSignMapper, Roo
return roomBoomSignMapper.getOneByRecordIdAndLevel(recordId, level);
}
@Override
public IPage<RoomBoomSign> list(IPage<RoomBoomSign> page, Long roomUid, Long uid, Integer level, Date startTime, Date endTime, Integer partitionId) {
return baseMapper.list(page, roomUid, uid, level, startTime, endTime, partitionId);
}
private void sendAward(RoomBoomLevelAward award, Long uid, Long roomUid) {
try {
RewardDto rewardDto = new RewardDto();

View File

@@ -33,32 +33,4 @@
select * from room_boom_sign where record_id = #{recordId} and level = #{level} order by id limit 1
</select>
<select id="list" resultType="com.accompany.business.model.room.RoomBoomSign">
select r.*
from room_boom_sign r
left join users u on u.uid = r.room_uid
<where>
<if test="startTime != null">
and r.create_time >= #{startTime}
</if>
<if test="endTime != null">
and r.create_time &lt;= #{endTime}
</if>
<if test="partitionId != null">
and u.partition_id = #{partitionId}
</if>
<if test="level != null">
and r.level = #{level}
</if>
<if test="roomUid != null">
and r.room_uid = #{roomUid}
</if>
<if test="uid != null">
and r.uid = #{uid}
</if>
and r.`status` = 2
</where>
order by r.id desc
</select>
</mapper>

View File

@@ -30,11 +30,6 @@ public class WebMVCConfig implements WebMvcConfigurer {
return new ModelHallAuthInterceptor();
}
@Bean
public WebInterceptor getWebInterceptor() {
return new WebInterceptor();
}
@Bean
public XssInterceptor getXssInterceptor() {
return new XssInterceptor();
@@ -55,7 +50,6 @@ public class WebMVCConfig implements WebMvcConfigurer {
registry.addInterceptor(getLoginInterceptor()).addPathPatterns("/**");
//registry.addInterceptor(getAppVersionInterceptor());
registry.addInterceptor(getModelHallAuthInterceptor());
registry.addInterceptor(getWebInterceptor());
registry.addInterceptor(getXssInterceptor());
}

View File

@@ -1,39 +0,0 @@
package com.accompany.business.controller.activities;
import com.accompany.business.model.activity.PageActivity;
import com.accompany.business.service.activity.PageActivityService;
import com.accompany.business.vo.activities.PageActivityVO;
import com.accompany.common.result.BusiResult;
import com.accompany.core.util.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 页面静态活动
*
* @author linuxea
* @date 2019/9/29 17:44
*/
@RestController
@RequestMapping("/page/static/activity")
public class PageActivityController {
@Autowired
private PageActivityService pageActivityService;
/**
* 通过活动标识 code 获取
*
* @param code 活动标识
* @return {@link PageActivityVO}
*/
@GetMapping("/{code}")
public BusiResult<PageActivityVO> getByCode(@PathVariable("code") String code) {
PageActivity pageActivity = pageActivityService.getByCode(code);
PageActivityVO pageActivityVO = BeanUtils.map(pageActivity, PageActivityVO.class);
return new BusiResult<>(pageActivityVO);
}
}

View File

@@ -4,7 +4,6 @@ import com.accompany.business.common.BaseController;
import com.accompany.business.service.exchange.GoldExchangeDiamondService;
import com.accompany.business.vo.exchange.GoldExchangeDiamondVo;
import com.accompany.common.annotation.Authorization;
import com.accompany.common.annotation.H5Authorization;
import com.accompany.common.result.BusiResult;
import com.accompany.common.status.BusiStatus;
import com.accompany.core.exception.ServiceException;
@@ -29,7 +28,6 @@ public class GoldExchangeDiamondController extends BaseController {
private GoldExchangeDiamondService service;
@Authorization
@H5Authorization
@ApiOperation("获取配置")
@GetMapping(value = "/getConfig")
public BusiResult<GoldExchangeDiamondVo> getConfig(HttpServletRequest request) {
@@ -39,7 +37,6 @@ public class GoldExchangeDiamondController extends BaseController {
}
@Authorization
@H5Authorization
@ApiOperation("兑换")
@PostMapping(value = "/exchange")
public BusiResult<Void> exchange(HttpServletRequest request, Long goldNum, Long diamondNum) {

View File

@@ -6,7 +6,6 @@ import com.accompany.core.enumeration.BillObjTypeEnum;
import com.accompany.core.enumeration.BillTypeEnum;
import com.accompany.sharding.vo.BillRecordDateVo;
import com.accompany.common.annotation.Authorization;
import com.accompany.common.annotation.H5Authorization;
import com.accompany.common.result.BusiResult;
import com.accompany.common.status.BusiStatus;
import com.accompany.core.enumeration.CurrencyEnum;

View File

@@ -6,7 +6,6 @@ import com.accompany.business.service.room.RoomRevenueService;
import com.accompany.business.service.room.RoomService;
import com.accompany.business.service.user.UsersService;
import com.accompany.common.annotation.Authorization;
import com.accompany.common.annotation.H5Authorization;
import com.accompany.common.constant.Constant;
import com.accompany.common.result.BusiResult;
import com.accompany.common.status.BusiStatus;
@@ -44,7 +43,6 @@ public class RoomRevenueController extends BaseController {
@ApiOperation("获取本周牌照房流水")
@GetMapping(value = "/weekTotal")
@Authorization
@H5Authorization
public BusiResult weekTotalRevenue() {
Long uid = getUid();
logger.info("weekTotalRevenue, uid:{}", uid);
@@ -54,7 +52,6 @@ public class RoomRevenueController extends BaseController {
@ApiOperation("生成本周牌照房流水excel下载链接")
@GetMapping(value = "/exportExcel")
@Authorization
@H5Authorization
public BusiResult exportExcel(@RequestParam("start") String start, @RequestParam("end") String end, @RequestParam(value="erbanNo", required = false) Long erbanNo) throws Exception {
Long uid = getUid();
if (erbanNo != null) {
@@ -80,7 +77,6 @@ public class RoomRevenueController extends BaseController {
@ApiOperation("获取本周个播房流水")
@GetMapping(value = "/singleBroadcast/weekTotal")
@Authorization
@H5Authorization
public BusiResult SingleBroadcastweekTotalRevenue() {
Long uid = getUid();
logger.info("singleBroadcast weekTotalRevenue, uid:{}", uid);
@@ -90,7 +86,6 @@ public class RoomRevenueController extends BaseController {
@ApiOperation("生成本周个播房流水excel下载链接")
@GetMapping(value = "/singleroom/exportExcel")
@Authorization
@H5Authorization
public BusiResult singleroomExportExcel(@RequestParam("start") String start, @RequestParam("end") String end) throws Exception {
Long uid = getUid();
logger.info("singleroomExportExcel, uid:{}, start:{}, end:{}", uid, start, end);

View File

@@ -5,7 +5,6 @@ import com.accompany.business.service.purse.DiamondGiveHistoryService;
import com.accompany.business.vo.DiamondGiveHistoryPageVo;
import com.accompany.business.vo.SimpleUserVo;
import com.accompany.common.annotation.Authorization;
import com.accompany.common.annotation.H5Authorization;
import com.accompany.common.result.BusiResult;
import com.accompany.common.status.BusiStatus;
import com.accompany.core.exception.ServiceException;
@@ -33,7 +32,6 @@ public class DiamondGiveHistoryController extends BaseController {
private DiamondGiveHistoryService diamondGiveHistoryService;
@H5Authorization
@Authorization
@ApiOperation(value = "用户转赠记录", httpMethod = "GET")
@GetMapping("/giveRecord")
@@ -50,7 +48,6 @@ public class DiamondGiveHistoryController extends BaseController {
* @param pageSize
* @return
*/
@H5Authorization
@Authorization
@ApiOperation(value = "用户转赠详情记录", httpMethod = "GET")
@GetMapping("/giveRecordVoByType")
@@ -60,7 +57,6 @@ public class DiamondGiveHistoryController extends BaseController {
}
@H5Authorization
@Authorization
@ApiOperation(value = "用户转赠钻石操作", httpMethod = "POST")
@PostMapping("/give")
@@ -70,7 +66,6 @@ public class DiamondGiveHistoryController extends BaseController {
return new BusiResult(BusiStatus.SUCCESS);
}
@H5Authorization
@Authorization
@ApiOperation(value = "用户转赠礼物操作", httpMethod = "POST")
@PostMapping("/giveGift")
@@ -83,7 +78,6 @@ public class DiamondGiveHistoryController extends BaseController {
@ApiOperation(value = "转赠钻石/礼物 -> 用户搜索", httpMethod = "POST")
@Authorization
@H5Authorization
@PostMapping("/searchUser")
public BusiResult<SimpleUserVo> searchUser(Long erbanNo) {
Long uid = this.getUid();
@@ -93,7 +87,6 @@ public class DiamondGiveHistoryController extends BaseController {
return new BusiResult<>(diamondGiveHistoryService.searchUser(uid, erbanNo));
}
@H5Authorization
@Authorization
@ApiOperation(value = "用户转赠历史", httpMethod = "GET")
@ApiImplicitParams({

View File

@@ -5,7 +5,6 @@ import com.accompany.business.service.purse.UserPurseService;
import com.accompany.business.vo.UserPurseVo;
import com.accompany.business.vo.UserPurseWithRoomTypeVo;
import com.accompany.common.annotation.Authorization;
import com.accompany.common.annotation.H5Authorization;
import com.accompany.common.result.BusiResult;
import com.accompany.common.status.BusiStatus;
import lombok.extern.slf4j.Slf4j;
@@ -24,7 +23,6 @@ public class UserPurseController extends BaseController {
private UserPurseService userPurseService;
@GetMapping({"query", "query2"})
@H5Authorization
@Authorization
public BusiResult<UserPurseVo> queryMyUserPurse(HttpServletRequest request){
Long uid = getUid(request);
@@ -36,7 +34,6 @@ public class UserPurseController extends BaseController {
}
@GetMapping("queryWithRoomType")
@H5Authorization
@Authorization
public BusiResult<UserPurseWithRoomTypeVo> queryMyUserPurseWithRoomType(HttpServletRequest request){
Long uid = getUid(request);

View File

@@ -69,19 +69,6 @@ public abstract class BasicInterceptor implements HandlerInterceptor {
return ticket;
}
/**
* 获取@H5Authorization注解需要的h5_token
* @param request
* @return
*/
public String getH5Token(HttpServletRequest request) {
String token = request.getHeader(ApplicationConstant.PublicParameters.H5_TOKEN);
if (StringUtils.isBlank(token)) {
token = request.getParameter(ApplicationConstant.PublicParameters.H5_TOKEN);
}
return token;
}
/**
* 返回重新登录提示内容

View File

@@ -3,7 +3,6 @@ package com.accompany.business.interceptor;
import cn.hutool.core.util.StrUtil;
import com.accompany.common.annotation.Authorization;
import com.accompany.common.annotation.H5Authorization;
import com.accompany.common.constant.ApplicationConstant;
import com.accompany.common.redis.RedisKey;
import com.accompany.common.utils.EnvComponent;
@@ -60,13 +59,6 @@ public class LoginInterceptor extends BasicInterceptor {
if (method.getAnnotation(Authorization.class) == null) {
return true;
}
// 如果同时有H5Authorization注解并且h5_token不为空使用H5Authorization校验
if (method.getAnnotation(H5Authorization.class) != null && StringUtils.isNotBlank(getH5Token(request))) {
// 如果请求头部信息同时有ticket和token则会使用Authorization注解校验用户登录信息
if (StringUtils.isBlank(getTicket(request))) {
return true;
}
}
String uid = this.getUid(request);
if (StringUtils.isEmpty(uid) || StringUtils.equalsIgnoreCase(uid, "null") || !StringUtils.isNumeric(uid)) {
logger.warn("uid illegal, uri={}, uid={}", request.getRequestURI(), uid);
@@ -121,8 +113,4 @@ public class LoginInterceptor extends BasicInterceptor {
return ticketStr;
}
private String getH5JwtToken(HttpServletRequest request) {
return request.getParameter(ApplicationConstant.PublicParameters.H5_TOKEN);
}
}

View File

@@ -1,105 +0,0 @@
package com.accompany.business.interceptor;
import com.accompany.common.annotation.Authorization;
import com.accompany.common.annotation.H5Authorization;
import com.accompany.common.constant.ApplicationConstant;
import com.accompany.common.redis.RedisKey;
import com.accompany.common.utils.StringUtils;
import com.accompany.core.service.common.JedisService;
import com.accompany.core.util.JwtUtils;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.SignatureException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.method.HandlerMethod;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
/**
* Created by yuanyi on 2019/3/26.
*/
public class WebInterceptor extends BasicInterceptor {
private static final Logger logger = LoggerFactory.getLogger(LoginInterceptor.class);
@Autowired
private JedisService jedisService;
@Autowired
private JwtUtils jwtUtils;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (!(handler instanceof HandlerMethod)) {
return true;
}
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
// 不需要登录校验
if (method.getAnnotation(H5Authorization.class) == null) {
return true;
}
// 如果同时有Authorization注解并且ticket不为空使用Authorization校验
if (method.getAnnotation(Authorization.class) != null && StringUtils.isNotBlank(getTicket(request))) {
return true;
}
String uid = this.getUid(request);
if (StringUtils.isEmpty(uid) || StringUtils.equalsIgnoreCase(uid, "null") || !StringUtils.isNumeric(uid)) {
logger.warn("uid illegal, uri={}, uid={}", request.getRequestURI(), uid);
writeLoginExpireResponse(response, 401, "Login status has expired, please log in again ~");
return false;
}
// h5登录校验
if (method.getAnnotation(H5Authorization.class) != null) {
String token = getH5Token(request);
if (StringUtils.isEmpty(token) || StringUtils.equalsIgnoreCase(token, "null")) {
logger.warn("jwtToken is null, uri={}, uid={}", request.getRequestURI(), uid);
writeLoginExpireResponse(response, 401, "Login status has expired, please log in again ~");
return false;
}
String realToken = this.jedisService.hget(RedisKey.h5loginjwtoken.getKey(), uid);
if (StringUtils.isEmpty(realToken)) {
logger.warn("jwtToken is not exists, uri={}, uid={}, token={}", request.getRequestURI(), uid, token);
writeLoginExpireResponse(response, 401, "Login status has expired, please log in again ~");
return false;
}
try {
jwtUtils.parseJWT(token);
} catch (ExpiredJwtException e) {
logger.error("jwtToken is expired,uid={},token={}", uid, token);
writeLoginExpireResponse(response, 406, "need login!");
return false;
} catch (SignatureException e) {
logger.error("signature is illegal,uid={},token={}", uid, token);
writeLoginExpireResponse(response, 407, "Login status has expired, please log in again ~");
return false;
}
if (!realToken.equals(token)) {
logger.warn("jwtToken illegal, uri={}, uid={}, token={}", request.getRequestURI(), uid, token);
writeLoginExpireResponse(response, 401, "Login status has expired, please log in again ~");
return false;
}
return true;
}
return true;
}
/**
* 获取 uid 以业务参数为首选
*
* @param request
* @return
*/
private String getUid(HttpServletRequest request) {
String uidStr = request.getParameter(ApplicationConstant.PublicParameters.UID);
if (StringUtils.isEmpty(uidStr)) {
uidStr = request.getHeader(ApplicationConstant.PublicParameters.PUB_UID);
}
return uidStr;
}
}

View File

@@ -45,7 +45,6 @@
<hibernate-validator.version>6.0.17.Final</hibernate-validator.version>
<hanlp.version>portable-1.6.8</hanlp.version>
<hanlp-lucene-plugin.version>1.1.6</hanlp-lucene-plugin.version>
<jjwt.version>0.9.1</jjwt.version>
<java-jwt.version>3.10.3</java-jwt.version>
<sitemesh.version>2.4.2</sitemesh.version>
<pagehelper.version>6.1.0</pagehelper.version>
@@ -78,6 +77,7 @@
<pinyin4j.version>2.5.1</pinyin4j.version>
<aws.version>2.30.37</aws.version>
<dynamic-tp.version>1.2.2</dynamic-tp.version>
<jjwt.version>0.12.5</jjwt.version>
</properties>
<dependencyManagement>

View File

@@ -0,0 +1,35 @@
<?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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.accompany</groupId>
<artifactId>accompany-oauth</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>accompany-oauth-sdk</artifactId>
<packaging>jar</packaging>
<description>OAuth SDK模块 - 数据传输对象和接口定义</description>
<dependencies>
<dependency>
<groupId>com.accompany</groupId>
<artifactId>accompany-core</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>com.accompany</groupId>
<artifactId>accompany-basic-sdk</artifactId>
<version>${revision}</version>
</dependency>
<!-- JWT依赖 -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
</dependency>
<!-- 移除Spring Security OAuth2依赖使用轻量化实现 -->
</dependencies>
</project>

View File

@@ -0,0 +1,59 @@
package com.accompany.oauth.constant;
/**
* 认证类型枚举
*
* @author Accompany OAuth Team
* @since 1.0.0
*/
public enum GrantTypeEnum {
/**
* 密码登录
*/
PASSWORD("password", "密码登录"),
/**
* 验证码登录
*/
VERIFY_CODE("verify_code", "验证码登录"),
/**
* 邮箱登录
*/
EMAIL("email", "邮箱登录"),
/**
* OpenID登录
*/
OPENID("openid", "OpenID登录"),
/**
* Apple登录
*/
APPLE("apple", "Apple登录");
private final String code;
private final String description;
GrantTypeEnum(String code, String description) {
this.code = code;
this.description = description;
}
public String getCode() {
return code;
}
public String getDescription() {
return description;
}
public static GrantTypeEnum fromCode(String code) {
for (GrantTypeEnum type : values()) {
if (type.code.equals(code)) {
return type;
}
}
throw new IllegalArgumentException("未知的认证类型: " + code);
}
}

View File

@@ -0,0 +1,82 @@
package com.accompany.oauth.constant;
import java.util.Arrays;
import java.util.Optional;
/**
* 登录类型
*/
public enum LoginTypeEnum {
/**
* 微信
*/
WECHAT((byte) 1),
/**
* QQ
*/
QQ((byte) 2),
/**
* ID萌声号或手机号
*/
ID((byte) 3),
/**
* 手机号一键登录
*/
PHONE((byte) 4),
/**
* 苹果账户
*/
APPLE((byte) 5),
/**
* 冷启动ticket登录
*/
TICKET((byte) 6),
/**
* 账号密码登录
*/
PASSWORD((byte) 7),
/**
* 谷歌登录
*/
GOOGLE((byte) 8),
/**
* line登录
*/
LINE((byte) 9),
/**
* facebook登录
*/
FACEBOOK((byte) 10),
/**
* 邮箱登录
* */
EMAIL((byte) 11),
;
private final byte value;
LoginTypeEnum(byte value) {
this.value = value;
}
public byte getValue() {
return value;
}
public static LoginTypeEnum get(int value) {
Optional<LoginTypeEnum> result = Arrays.stream(LoginTypeEnum.values()).filter(loginTypeEnum ->
loginTypeEnum.value == value).findAny();
if (result.isPresent()) {
return result.get();
}
throw new IllegalArgumentException("value not match");
}
}

View File

@@ -0,0 +1,54 @@
package com.accompany.oauth.dto;
import lombok.Data;
/**
* 认证结果DTO
*
* @author Accompany OAuth Team
* @since 1.0.0
*/
@Data
public class AuthResult {
/**
* 访问令牌
*/
private String accessToken;
/**
* 刷新令牌
*/
private String refreshToken;
/**
* 过期时间(秒)
*/
private Long expiresIn;
/**
* 令牌类型
*/
private String tokenType = "Bearer";
/**
* 权限范围
*/
private String scope;
/**
* 用户ID (兼容oauth2)
*/
private Long uid;
/**
* 网易云信Token (兼容oauth2)
*/
private String netEaseToken = "";
/**
* JWT ID (兼容oauth2)
*/
private String jti = "";
}

View File

@@ -0,0 +1,22 @@
package com.accompany.oauth.dto;
public class DayIpMaxRegisterLimitConfig {
private boolean open;
private long max;
public boolean getOpen() {
return open;
}
public void setOpen(boolean open) {
this.open = open;
}
public long getMax() {
return max;
}
public void setMax(long max) {
this.max = max;
}
}

View File

@@ -0,0 +1,10 @@
package com.accompany.oauth.dto;
import lombok.Data;
@Data
public class RepeatedDeviceIpRegisterLimitConfig {
private boolean open;
private int repeatedDeviceNumLimit;
private int repeatedIpNumLimit;
}

View File

@@ -0,0 +1,46 @@
package com.accompany.oauth.dto;
import com.accompany.oauth.ticket.Ticket;
import lombok.Data;
import java.util.List;
/**
* 票据签发响应VO
*
* @author Accompany OAuth Team
* @since 1.0.0
*/
@Data
public class TicketResponseVO {
/**
* 票据列表
*/
private List<TicketVO> tickets;
/**
* 用户ID
*/
private Long uid;
/**
* 签发类型
*/
private String issue_type = Ticket.MULTI_TYPE;
/**
* 票据信息VO
*/
@Data
public static class TicketVO {
/**
* 票据值
*/
private String ticket;
/**
* 过期时间(秒)
*/
private Integer expiresIn;
}
}

View File

@@ -0,0 +1,50 @@
package com.accompany.oauth.exception;
import com.accompany.common.status.BusiStatus;
import java.util.Map;
/**
* OAuth认证异常基类
*
* @author Accompany OAuth Team
* @since 1.0.0
*/
public class OAuthException extends RuntimeException {
private BusiStatus busiStatus;
private Map<String, String> additionalInformation;
public OAuthException(BusiStatus busiStatus) {
super(busiStatus.getMessage());
this.busiStatus = busiStatus;
}
public OAuthException(BusiStatus busiStatus, String message) {
super(message);
this.busiStatus = busiStatus;
}
public OAuthException(BusiStatus busiStatus, Map<String, String> additionalInformation) {
super(busiStatus.getMessage());
this.busiStatus = busiStatus;
this.additionalInformation = additionalInformation;
}
public BusiStatus getBusiStatus() {
return busiStatus;
}
public void setBusiStatus(BusiStatus busiStatus) {
this.busiStatus = busiStatus;
}
public Map<String, String> getAdditionalInformation() {
return additionalInformation;
}
public void setAdditionalInformation(Map<String, String> additionalInformation) {
this.additionalInformation = additionalInformation;
}
}

View File

@@ -0,0 +1,39 @@
package com.accompany.oauth.model;
import lombok.Data;
/**
* Token对模型
*
* @author Accompany OAuth Team
* @since 1.0.0
*/
@Data
public class TokenPair {
/**
* 访问令牌
*/
private String accessToken;
/**
* 刷新令牌
*/
private String refreshToken;
/**
* 过期时间(秒)
*/
private Long expiresIn;
/**
* 令牌类型
*/
private String tokenType = "Bearer";
/**
* 权限范围
*/
private String scope;
}

View File

@@ -0,0 +1,60 @@
package com.accompany.oauth.model;
import lombok.Data;
import java.util.Date;
import java.util.Set;
/**
* Token验证结果模型
*
* @author Accompany OAuth Team
* @since 1.0.0
*/
@Data
public class TokenValidation {
/**
* 是否有效
*/
private boolean valid;
/**
* 用户ID
*/
private Long userId;
/**
* 权限范围
*/
private Set<String> scopes;
/**
* 过期时间
*/
private Date expirationTime;
/**
* 客户端ID
*/
private String clientId;
/**
* 错误信息
*/
private String errorMessage;
public TokenValidation(boolean valid) {
this.valid = valid;
}
public static TokenValidation valid(Long userId, Date expirationTime, String clientId) {
TokenValidation validation = new TokenValidation(true);
validation.setUserId(userId);
validation.setExpirationTime(expirationTime);
validation.setClientId(clientId);
return validation;
}
}

View File

@@ -0,0 +1,99 @@
package com.accompany.oauth.ticket;
import java.io.Serializable;
import java.util.*;
/**
* Ticket类 - OAuth访问票据
* 合并了接口和默认实现的功能
*
* @author Accompany OAuth Team
* @since 1.0.0
*/
public class Ticket implements Serializable {
public static final String ONCE_TYPE = "once";
public static final String MULTI_TYPE = "multi";
private String value;
private Date expiration;
private String ticketType = ONCE_TYPE.toLowerCase();
private String accessToken;
private Set<String> scope;
private Map<String, Object> additionalInformation = Collections.emptyMap();
/**
* 创建票据
*/
public Ticket(String value) {
this.value = value;
}
/**
* 复制构造函数
*/
public Ticket(Ticket ticket) {
this(ticket.getValue());
setAdditionalInformation(ticket.getAdditionalInformation());
setAccessToken(ticket.getAccessToken());
setExpiration(ticket.getExpiration());
setScope(ticket.getScope());
setTicketType(ticket.getTicketType());
}
public void setAdditionalInformation(Map<String, Object> additionalInformation) {
this.additionalInformation = new LinkedHashMap<>(additionalInformation);
}
public Map<String, Object> getAdditionalInformation() {
return additionalInformation;
}
public void setAccessToken(String accessToken) {
this.accessToken = accessToken;
}
public String getAccessToken() {
return accessToken;
}
public void setTicketType(String ticketType) {
this.ticketType = ticketType;
}
public String getTicketType() {
return ticketType;
}
public void setExpiration(Date expiration) {
this.expiration = expiration;
}
public Date getExpiration() {
return expiration;
}
public void setExpiresIn(int delta) {
setExpiration(new Date(System.currentTimeMillis() + delta * 1000L));
}
public int getExpiresIn() {
return expiration != null ? Long.valueOf((expiration.getTime() - System.currentTimeMillis()) / 1000L)
.intValue() : 0;
}
public String getValue() {
return this.value;
}
public void setValue(String value) {
this.value = value;
}
public void setScope(Set<String> scope) {
this.scope = scope;
}
public Set<String> getScope() {
return scope;
}
}

View File

@@ -0,0 +1,85 @@
<?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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.accompany</groupId>
<artifactId>accompany-oauth</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>accompany-oauth-service</artifactId>
<packaging>jar</packaging>
<description>OAuth Service模块 - 业务逻辑实现</description>
<dependencies>
<dependency>
<groupId>com.accompany</groupId>
<artifactId>accompany-core</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>com.accompany</groupId>
<artifactId>accompany-oauth-sdk</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>com.accompany</groupId>
<artifactId>accompany-basic-service</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>com.accompany</groupId>
<artifactId>accompany-sms-service</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>com.accompany</groupId>
<artifactId>accompany-email-service</artifactId>
<version>${revision}</version>
</dependency>
<!-- Redis支持 - 使用Redisson -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
</dependency>
<!-- 手机号验证支持 -->
<dependency>
<groupId>com.googlecode.libphonenumber</groupId>
<artifactId>libphonenumber</artifactId>
</dependency>
<!-- Spring Boot Web支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 移除Spring Security相关依赖使用轻量化实现 -->
<!-- JWT 支持API -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>${jjwt.version}</version>
</dependency>
<!-- JWT 支持:实现 -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>${jjwt.version}</version>
<scope>runtime</scope>
</dependency>
<!-- JWT 支持JSON 序列化/反序列化(选一个)-->
<!-- 如果你项目用 JacksonSpring Boot 默认),选这个 -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>${jjwt.version}</version>
<scope>runtime</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,20 @@
package com.accompany.oauth.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Configuration;
@Data
@Configuration
@RefreshScope
@ConfigurationProperties("oauth2.server")
public class OAuthConfig {
private String clientId;
private String clientSecret;
private String jwtSignKey;
}

View File

@@ -0,0 +1,208 @@
package com.accompany.oauth.service;
import com.accompany.common.constant.Constant;
import com.accompany.common.device.DeviceInfo;
import com.accompany.common.redis.RedisKey;
import com.accompany.common.status.BusiStatus;
import com.accompany.core.enumeration.PartitionEnum;
import com.accompany.core.exception.ServiceException;
import com.accompany.core.model.Account;
import com.accompany.core.model.AccountLoginRecord;
import com.accompany.core.model.Users;
import com.accompany.core.service.SysConfService;
import com.accompany.core.service.account.AccountBlockCheckService;
import com.accompany.core.service.account.AccountService;
import com.accompany.core.service.account.LoginRecordService;
import com.accompany.core.service.account.UserAppService;
import com.accompany.core.service.common.JedisService;
import com.accompany.core.service.region.RegionNetworkService;
import com.accompany.core.service.user.UsersBaseService;
import com.accompany.core.util.I18NMessageSourceUtil;
import com.accompany.email.service.EmailService;
import com.accompany.oauth.constant.LoginTypeEnum;
import com.accompany.oauth.exception.OAuthException;
import com.accompany.sms.service.SmsService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Map;
import static com.accompany.core.enumeration.I18nAlertEnum.ACCOUNT_LOGIN_BLOCK_MSG;
@Slf4j
@Service
public class AccountLoginService {
@Autowired
private JedisService jedisService;
@Autowired
private AccountService accountService;
@Autowired
private AccountManageService accountManageService;
@Autowired
private AccountBlockCheckService accountBlockCheckService;
@Autowired
private UserAppService userAppService;
@Autowired
private LoginRecordService loginRecordService;
@Autowired
private UsersBaseService usersBaseService;
@Autowired
private SmsService smsService;
@Autowired
private EmailService emailService;
@Autowired
private SysConfService sysConfService;
@Autowired
private RegionNetworkService regionService;
/**
* 不允许登录的用户账号类型
*/
private final static List<Byte> NEED_INTERCEPT_USER_TYPE = Arrays.asList(Constant.DefUser.OFFICIAL,
Constant.DefUser.ROBOT, Constant.DefUser.GAME_MANAGE_ROBOT);
public void login(Account account, LoginTypeEnum loginType, DeviceInfo deviceInfo, String openId) {
Long uid = account.getUid();
String deviceId = deviceInfo.getDeviceId();
String ip = deviceInfo.getClientIp();
String app = deviceInfo.getApp();
String appVersion = deviceInfo.getAppVersion();
Date date = new Date();
// 拦截指定账号登录
Users users = usersBaseService.getUsersByUid(account.getUid());
if (users != null && NEED_INTERCEPT_USER_TYPE.contains(users.getDefUser())) {
throw new ServiceException(BusiStatus.ILLEGAL_OPERATE);
}
Long blockEndTime = accountBlockCheckService.checkReturnEndTime(account.getErbanNo(), account.getPhone(), account.getEmail(), deviceId, ip);
//检查账号、设备号、号段是否封禁
if (null != blockEndTime){
Integer partitionId = null != users? users.getPartitionId(): PartitionEnum.ENGLISH.getId();
Map<String, String> tipMap = Map.of("reason", I18NMessageSourceUtil.getMessage(ACCOUNT_LOGIN_BLOCK_MSG, new Object[]{users.getErbanNo()}, partitionId), "date", String.valueOf(blockEndTime));
throw new OAuthException(BusiStatus.ACCOUNT_ERROR, tipMap);
}
accountManageService.checkAccountCancel(uid);
//更新account信息
String newToken = accountService.refreshAndGetNetEaseToken(account);
account.setNeteaseToken(newToken);
account.setApp(deviceInfo.getApp());
account.setAppVersion(deviceInfo.getAppVersion());
account.setChannel(deviceInfo.getChannel());
account.setLinkedmeChannel(deviceInfo.getLinkedmeChannel());
account.setDeviceId(deviceInfo.getDeviceId());
account.setImei(deviceInfo.getImei());
account.setIspType(deviceInfo.getIspType());
account.setModel(deviceInfo.getModel());
account.setNetType(deviceInfo.getNetType());
account.setOs(deviceInfo.getOs());
account.setOsversion(deviceInfo.getOsVersion());
account.setUpdateTime(date);
account.setLastLoginTime(date);
account.setLastLoginIp(ip);
accountService.updateById(account);
//更新用户正在使用的app字段
userAppService.updateCurrentApp(uid, app, date, ip, appVersion);
//将用户信息登记
AccountLoginRecord accountLoginRecord = buildAccountLoginRecord(ip, account, loginType.getValue(), deviceInfo, openId);
loginRecordService.addAccountLoginRecordAsync(accountLoginRecord);
}
public AccountLoginRecord buildAccountLoginRecord(String ipAddress, Account account, byte loginType, DeviceInfo deviceInfo, String openId) {
AccountLoginRecord accountLoginRecord = new AccountLoginRecord();
accountLoginRecord.setLoginIp(ipAddress);
accountLoginRecord.setLoginIpRegion(regionService.getRegion(ipAddress));
accountLoginRecord.setUid(account.getUid());
accountLoginRecord.setErbanNo(account.getErbanNo());
accountLoginRecord.setLoginType(loginType);
accountLoginRecord.setDeviceId(deviceInfo.getDeviceId());
accountLoginRecord.setPhone(account.getPhone());
accountLoginRecord.setEmail(account.getEmail());
accountLoginRecord.setApp(account.getApp());
accountLoginRecord.setAppVersion(deviceInfo.getAppVersion());
accountLoginRecord.setIspType(deviceInfo.getIspType());
accountLoginRecord.setModel(deviceInfo.getModel());
accountLoginRecord.setOs(deviceInfo.getOs());
accountLoginRecord.setOsversion(deviceInfo.getOsVersion());
accountLoginRecord.setCreateTime(new Date());
if (loginType == LoginTypeEnum.APPLE.getValue()) {
accountLoginRecord.setAppleUid(openId);
}
return accountLoginRecord;
}
/**
* 普通用户需要用手机验证码登录,官方账号和公会账号不校验验证码
*
* @param account
* @param code
*/
public void checkCodeByUserType(Account account, String code, LoginTypeEnum loginType) {
//是否手机号登录
boolean needVerifyCode = LoginTypeEnum.PHONE.getValue() == loginType.getValue()
|| LoginTypeEnum.EMAIL.getValue() == loginType.getValue();
if (!needVerifyCode) {
return;
}
if (!StringUtils.hasText(code)) {
throw new OAuthException(BusiStatus.VERIFY_CODE_ERROR);
}
boolean verifyResult = LoginTypeEnum.PHONE.getValue() == loginType.getValue()?
smsService.verifySmsCode(account.getPhone(), code):
emailService.verifyEmailCode(account.getEmail(), code);
if (!verifyResult) {
throw new OAuthException(BusiStatus.VERIFY_CODE_ERROR);
}
}
/**
* 处理密码登录
*
* @param username 用户登录账号
* @param accountPassword
* @return 错误提示
*/
public void validPwd(String username, String password, String accountPassword) {
String value = sysConfService.getDefaultSysConfValueById(Constant.SysConfId.PWD_LOGIN_DAY_WRONG_COUNT, "5");
Long maxCount = Long.valueOf(value);
String cacheKey = RedisKey.user_login_pwd_wrong_day_count.getKey();
Boolean exits = jedisService.exits(cacheKey);
String countValue = jedisService.hget(cacheKey, username);
Long currCount = StringUtils.isEmpty(countValue) ? 0L : Long.parseLong(countValue);
if (currCount >= maxCount) {
throw new OAuthException(BusiStatus.PWD_WRONG_OVER_LIMIT);
}
if (!password.equals(accountPassword)) {
currCount = jedisService.hincrBy(cacheKey, username, 1L);
if (!exits) {
jedisService.expire(cacheKey, 10 * 60);//10分钟后解锁
}
if (currCount >= maxCount) {
throw new OAuthException(BusiStatus.PWD_WRONG_OVER_LIMIT);
} else {
Long remainCount = maxCount - currCount;
throw new OAuthException(BusiStatus.PASSWORD_ERROR_COUNT, String.format(BusiStatus.PASSWORD_ERROR_COUNT.getMessage(), remainCount));
}
}
}
}

View File

@@ -0,0 +1,509 @@
package com.accompany.oauth.service;
import cn.hutool.core.util.ObjectUtil;
import com.accompany.common.constant.Constant;
import com.accompany.common.device.DeviceInfo;
import com.accompany.common.netease.neteaseacc.result.TokenRet;
import com.accompany.common.redis.RedisKey;
import com.accompany.common.status.BusiStatus;
import com.accompany.common.utils.BlankUtil;
import com.accompany.common.utils.CommonUtil;
import com.accompany.common.utils.DateTimeUtil;
import com.accompany.common.utils.UUIDUtil;
import com.accompany.core.exception.ServiceException;
import com.accompany.core.model.Account;
import com.accompany.core.model.GoogleOpenidRef;
import com.accompany.core.model.UserCancelRecord;
import com.accompany.core.model.Users;
import com.accompany.core.mybatismapper.AccountMapper;
import com.accompany.core.service.SysConfService;
import com.accompany.core.service.account.AccountService;
import com.accompany.core.service.account.ErBanNoService;
import com.accompany.core.service.account.NetEaseService;
import com.accompany.core.service.common.JedisService;
import com.accompany.core.service.user.GoogleOpenidRefService;
import com.accompany.core.service.user.UserCancelRecordService;
import com.accompany.core.service.user.UsersBaseService;
import com.accompany.core.util.MD5;
import com.accompany.email.service.EmailService;
import com.accompany.oauth.constant.LoginTypeEnum;
import com.accompany.oauth.dto.DayIpMaxRegisterLimitConfig;
import com.accompany.oauth.dto.RepeatedDeviceIpRegisterLimitConfig;
import com.accompany.oauth.exception.OAuthException;
import com.accompany.sms.service.SmsService;
import com.alibaba.fastjson.JSON;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.util.Date;
import java.util.Map;
/**
* @author liuguofu
* on 2015/3/20.
*/
@Slf4j
@Service
public class AccountManageService {
@Autowired
private JedisService jedisService;
@Autowired
private AccountMapper accountMapper;
@Autowired
private NetEaseService netEaseService;
@Autowired
private ErBanNoService erBanNoService;
@Autowired
private AccountService accountService;
@Autowired
private UsersBaseService usersBaseService;
@Autowired
private UserCancelRecordService userCancelRecordService;
@Autowired
private SysConfService sysConfService;
@Autowired
private SmsService smsService;
@Autowired
private EmailService emailService;
@Autowired
private GoogleOpenidRefService googleOpenidRefService;
public Account getAccountPyUid(Long uid) {
return accountService.getAccountByUid(uid);
}
public Account getAccountPyUsername(String username, String password) {
log.info("getAccountByUserName username:{} password:{}", username, password);
Long erbanNo = Long.parseLong(username);
return accountService.getAccountByErBanNo(erbanNo);
}
public Account getOrGenAccountByPhone(String phone, String phoneAreaCode, String smsCode, DeviceInfo deviceInfo) {
log.info("getOrGenAccountByPhone phone:{},smsCode:{},phoneAreaCode{}", phone, smsCode, phoneAreaCode);
final String lockVal = jedisService.lock(RedisKey.lock_register_by_phone.getKey(phone));
try {
if (BlankUtil.isBlank(lockVal)) {
throw new ServiceException(BusiStatus.REQUEST_FAST);
}
Account account = accountService.getAccountByPhone(phone);
if (account == null) {
account = saveSignUpByPhone(phone, phoneAreaCode, deviceInfo);
}
return account;
} finally {
jedisService.unlock(RedisKey.lock_register_by_phone.getKey(phone), lockVal);
}
}
public Account getOrGenAccountByEmail(String email, String code, DeviceInfo deviceInfo, String ipAddress) {
log.info("getOrGenAccountByPhone email:{},code:{}", email, code);
String lockVal = jedisService.lock(RedisKey.lock_register_by_email.getKey(email));
try {
if (BlankUtil.isBlank(lockVal)) {
throw new ServiceException(BusiStatus.REQUEST_FAST);
}
Account account = accountService.getAccountByEmail(email);
if (account == null) {
account = saveSignUpByEmail(email, null, deviceInfo, ipAddress);
}
return account;
} finally {
jedisService.unlock(RedisKey.lock_register_by_email.getKey(email), lockVal);
}
}
@SneakyThrows
public Account getOrGenAccountByOpenid(Byte type, String openid, String unionId, String idToken, DeviceInfo deviceInfo) {
log.info("getOrGenAccountByOpenid openId:{},type:{},unionId:{}", openid, type, unionId);
final String locKey = RedisKey.lock_register_by_openid.getKey(openid, unionId, String.valueOf(type));
final String lockVal = jedisService.lock(locKey, 10 * 1000);
try {
if (BlankUtil.isBlank(lockVal)) {
throw new ServiceException(BusiStatus.REQUEST_FAST);
}
Account account = null;
String thirdAccountEmail = null;
// openid是邮箱则是新版本
if (LoginTypeEnum.GOOGLE.getValue() == type && emailService.isValidEmail(openid)) {
GoogleOpenidRef ref = googleOpenidRefService.getRefByEmail(openid, idToken);
if (null != ref) {
openid = ref.getOpenId();
unionId = ref.getOpenId();
thirdAccountEmail = ref.getEmail();
if (ref.isRegister()){
Account emailAccount = accountService.getAccountByEmail(thirdAccountEmail);
if (null != emailAccount && null == emailAccount.getThirdLoginType()){
emailAccount.setThirdLoginType(type);
emailAccount.setUnionId(unionId);
emailAccount.setOpenId(openid);
accountMapper.updateById(emailAccount);
account = emailAccount;
}
}
}
}
if (null == account){
account = accountService.getAccountByThird(type, unionId);
}
if (null != account){
if (BlankUtil.isBlank(account.getUnionId()) || !account.getUnionId().equals(unionId)) {
account.setUnionId(unionId);
}
account.setLastLoginTime(new Date());
account.setLastLoginIp(deviceInfo.getClientIp());
account.setUpdateTime(new Date());
accountMapper.updateById(account);
return account;
}
checkRegisterLimit(deviceInfo.getDeviceId(), deviceInfo.getClientIp());
Date date = new Date();
account = new Account();
account.setLastLoginTime(date);
account.setLastLoginIp(deviceInfo.getClientIp());
account.setUpdateTime(date);
account.setRegisterIp(deviceInfo.getClientIp());
account.setSignTime(date);
account.setState(Constant.AccountState.normal);
account.setErbanNo(erBanNoService.getErBanNo());
account.setPhone(CommonUtil.genSpecialPhoneForInitAccount(account.getErbanNo().toString()));
// 三方登录信息
account.setThirdLoginType(type);
account.setUnionId(unionId);
account.setOpenId(openid);
account.setEmail(thirdAccountEmail);
account.setNeteaseToken(UUIDUtil.get());
account = fillDeviceInfo(account, deviceInfo);
accountMapper.insert(account);
//写缓存
accountService.writeAche(account);
String uidStr = String.valueOf(account.getUid());
TokenRet tokenRet = netEaseService.createNetEaseAcc(uidStr, account.getNeteaseToken(), "", "", null);
if (tokenRet.getCode() != 200) {
log.info("注册云信账号失败,openid=" + openid + "&uid=" + uidStr + ",异常原因code=" + tokenRet.getCode());
log.error("注册云信账号失败,openid=" + openid + "&uid=" + uidStr + ",异常原因code=" + tokenRet.getCode());
throw new ServiceException("第三方登录失败,openid=" + openid + ",异常原因code=" + tokenRet.getCode());
}
return account;
} finally {
jedisService.unlock(locKey, lockVal);
}
}
private void checkRegisterLimit(String deviceId, String ipAddress) {
if (!StringUtils.hasText(deviceId)) {
throw new OAuthException(BusiStatus.DEVICE_ERROR);
}
RepeatedDeviceIpRegisterLimitConfig repeatedConfig = getRepeatedDeviceIpLimitConfig();
if (repeatedConfig.isOpen()) {
long repeatedDeviceNum = accountService.lambdaQuery().eq(Account::getDeviceId, deviceId).count();
if (repeatedDeviceNum >= repeatedConfig.getRepeatedDeviceNumLimit()) {
throw new OAuthException(BusiStatus.REGISTER_FREQUENT);
}
long repeatedIpNum = accountService.lambdaQuery().eq(Account::getRegisterIp, ipAddress).count();
if (repeatedIpNum >= repeatedConfig.getRepeatedIpNumLimit()) {
throw new OAuthException(BusiStatus.REGISTER_FREQUENT);
}
}
//当日单个ip注册数
DayIpMaxRegisterLimitConfig config = getIpMaxLimitConfig();
if (config.getOpen()) {
long count = accountService.getRegisterIpCountByOneDay(ipAddress);
if (count >= config.getMax()) {
throw new OAuthException(BusiStatus.REGISTER_FREQUENT);
}
}
}
/**
* 通过手机号码注册,独立账号系统,不掺杂业务
*
* @param phone
* @return
*/
@SneakyThrows
public Account saveSignUpByPhone(String phone, String phoneAreaCode, DeviceInfo deviceInfo) {
checkRegisterLimit(deviceInfo.getDeviceId(), deviceInfo.getClientIp());
Date date = new Date();
Account account = new Account();
account.setPhone(phone);
account.setPhoneAreaCode(phoneAreaCode);
// if (!StringUtils.isEmpty(password)) {
// account.setPassword(encryptPassword(password));
// }
account.setNeteaseToken(UUIDUtil.get());
account.setLastLoginTime(date);
account.setLastLoginIp(deviceInfo.getClientIp());
account.setUpdateTime(date);
account.setRegisterIp(deviceInfo.getClientIp());
account.setSignTime(date);
account.setState(Constant.AccountState.normal);
account.setErbanNo(erBanNoService.getErBanNo());
account = fillDeviceInfo(account, deviceInfo);
account.setSignupApp(deviceInfo.getApp());
accountMapper.insert(account);
accountService.writeAche(account);
String uidStr = String.valueOf(account.getUid());
TokenRet tokenRet = netEaseService.createNetEaseAcc(uidStr, account.getNeteaseToken(), "", "", null);
if (tokenRet.getCode() != 200) {
log.error("手机号码phone=" + phone + "注册异常,异常原因code=" + tokenRet.getCode());
log.info("手机号码phone=" + phone + "注册异常,异常原因code=" + tokenRet.getCode());
throw new OAuthException(BusiStatus.REGISTER_NETEASE_FAIL);
}
return account;
}
@SneakyThrows
private Account saveSignUpByEmail(String email, String password, DeviceInfo deviceInfo, String ipAddress) {
checkRegisterLimit(deviceInfo.getDeviceId(), ipAddress);
Date date = new Date();
Account account = new Account();
account.setEmail(email);
if (StringUtils.hasText(password)) {
account.setPassword(MD5.getMD5(password));
}
account.setNeteaseToken(UUIDUtil.get());
account.setLastLoginTime(date);
account.setLastLoginIp(ipAddress);
account.setUpdateTime(date);
account.setRegisterIp(ipAddress);
account.setSignTime(date);
account.setState(Constant.AccountState.normal);
account.setErbanNo(erBanNoService.getErBanNo());
account = fillDeviceInfo(account, deviceInfo);
accountMapper.insert(account);
accountService.writeAche(account);
String uidStr = String.valueOf(account.getUid());
TokenRet tokenRet = netEaseService.createNetEaseAcc(uidStr, account.getNeteaseToken(), "", "", null);
if (tokenRet.getCode() != 200) {
log.error("邮件email {} 注册异常,异常原因code {}", email, tokenRet.getCode());
throw new OAuthException(BusiStatus.REGISTER_NETEASE_FAIL);
}
return account;
}
private Account fillDeviceInfo(Account account, DeviceInfo deviceInfo) {
if (deviceInfo != null) {
account.setSignupApp(deviceInfo.getApp());
account.setApp(deviceInfo.getApp());
account.setAppVersion(deviceInfo.getAppVersion());
account.setChannel(deviceInfo.getChannel());
account.setLinkedmeChannel(deviceInfo.getLinkedmeChannel());
account.setDeviceId(deviceInfo.getDeviceId());
account.setImei(deviceInfo.getImei());
account.setIspType(deviceInfo.getIspType());
account.setModel(deviceInfo.getModel());
account.setNetType(deviceInfo.getNetType());
account.setOs(deviceInfo.getOs());
account.setOsversion(deviceInfo.getOsVersion());
account.setDeviceInfo(JSON.toJSONString(deviceInfo));
}
return account;
}
/**
* 重置密码
* 两个场景调用 => 客户端未登录 忘记密码, 此时uid 为 null 登录状态下忘记密码 uid有值
* @param uid
* @param phone
* @param password
* @param resetCode
* @return 1:成功 2重置码无效 3用户不存在
*/
public void resetPasswordByResetCode(Long uid, String phone, String password, String resetCode) {
if (phone.contains("*")) {
Account account = accountService.getById(uid);
if (account == null) {
throw new OAuthException(BusiStatus.USER_NOT_EXISTED);
}
phone = account.getPhone();
if (!CommonUtil.checkPhoneFormat(account.getPhoneAreaCode(), account.getPhone())) {
throw new OAuthException(BusiStatus.PHONE_INVALID);
}
}
long count = accountService.countByPhone(phone);
if (count > 1L) {
throw new OAuthException(BusiStatus.PHONE_BIND_TOO_MANY_ACCOUNT);
}
Account account = accountService.getAccountByPhone(phone);
if (null == account || (uid != null && !account.getUid().equals(uid))) {
throw new OAuthException(BusiStatus.PHONE_BIND_ERROR);
}
//检验验证码
if (!smsService.verifySmsCodeByCache(phone, resetCode)) {
throw new OAuthException(BusiStatus.INVALID_IDENTIFYING_CODE);
}
accountService.resetAccountPwd(account.getUid(), password);
//成功后删除验证码缓存
smsService.delSmsCodeCache(phone);
// 删除用户信息缓存
jedisService.hdel(RedisKey.user.getKey(), account.getUid().toString());
jedisService.hdel(RedisKey.user_summary.getKey(), account.getUid().toString());
accountService.delNickPasswordCache(account.getErbanNo());
}
/**
* 重置密码
* 两个场景调用 => 客户端未登录 忘记密码, 此时uid 为 null 登录状态下忘记密码 uid有值
* @param uid
* @param email
* @param password
* @param code
* @return 1:成功 2重置码无效 3用户不存在
*/
public void resetPasswordByEmailCode(Long uid, String email, String password, String code) {
emailService.validEmailAddress(email);
long count = accountService.countByEmail(email);
if (count > 1L) {
throw new OAuthException(BusiStatus.EMAIL_BIND_TOO_MANY_ACCOUNT);
}
Account account = accountService.getAccountByEmail(email);
if (null == account) {
throw new OAuthException(BusiStatus.ACCOUNT_NOT_BIND_EMAIL);
} else if (null != uid && !account.getUid().equals(uid)) {
throw new OAuthException(BusiStatus.ACCOUNT_BIND_EMAIL_DIFF);
}
//检验验证码
if (!emailService.verifyCodeByCache(email, code)) {
throw new OAuthException(BusiStatus.INVALID_IDENTIFYING_EMAIL_CODE);
}
accountService.resetAccountPwd(account.getUid(), password);
//成功后删除验证码缓存
emailService.delCodeCache(email);
// 删除用户信息缓存
jedisService.hdel(RedisKey.user.getKey(), account.getUid().toString());
jedisService.hdel(RedisKey.user_summary.getKey(), account.getUid().toString());
accountService.delNickPasswordCache(account.getErbanNo());
}
public void resetPasswordByOldPassword(String phone, String password, String newPassword) {
Account account = accountService.getAccountByPhone(phone);
if (null == account) {
throw new ServiceException(BusiStatus.USER_NOT_EXISTED);
}
String oldPwd = account.getPassword();
password = MD5.getMD5(password);
if (!StringUtils.hasText(password) || !password.equals(oldPwd)) {
throw new ServiceException(BusiStatus.OLD_PASSWORD_ERROR);
}
accountService.resetAccountPwd(account.getUid(), newPassword);
accountService.delNickPasswordCache(account.getErbanNo());
//记录最近30天内绑定手机号
jedisService.setex(RedisKey.modify_pwd_sign.getKey(String.valueOf(account.getUid())),
30 * 24 * 60 * 60, String.valueOf(new Date().getTime()));
}
/**
* 重置登录密码
* @param uid
* @param password
* @return
*/
public void setupInitialPassword(Long uid, String password) {
Account account = accountService.getById(uid);
if (account == null) {
throw new ServiceException(BusiStatus.INVALID_USER);
}
Boolean result = accountService.updateAccountPwd(account.getUid(), password);
if (!result) {
throw new ServiceException(BusiStatus.INVALID_REQUEST);
}
// 更新用户缓存
this.jedisService.hdel(RedisKey.user.getKey(), account.getUid().toString());
this.jedisService.hdel(RedisKey.user_summary.getKey(), account.getUid().toString());
accountService.delNickPasswordCache(account.getErbanNo());
}
public void checkAccountCancel(Long uid) {
log.info("检查账号{}是否已注销", uid);
Users users = usersBaseService.getUsersByUid(uid);
if (ObjectUtil.isNull(users)) {
log.info("获取不到用户{}users账号信息", uid);
return;
}
if (!Constant.UserUseStatus.cancel.equals(users.getUseStatus())) return;
UserCancelRecord userCancelRecord = userCancelRecordService.getById(uid);
if (ObjectUtil.isNull(userCancelRecord)) {
//获取不到注销账号信息
log.info("获取不到用户{}注销信息", uid);
throw new OAuthException(BusiStatus.ACCOUNT_CANCEL_INFO_NOT_EXIST);
}
log.info("检测到注销账号{}昵称{}于{}尝试登录", users.getErbanNo(), userCancelRecord.getNick(), DateTimeUtil.convertDate(userCancelRecord.getUpdateTime()));
Integer surviveTime = Integer.valueOf(sysConfService.getDefaultSysConfValueById(Constant.SysConfId.USER_RECOVER_CREDENTIALS_SURVIVE_TIME, String.valueOf(3 * 60)));
//写入凭证标识
jedisService.setex(RedisKey.cancel_user_recover_credentials.getKey(String.valueOf(users.getErbanNo())), surviveTime, String.valueOf(uid));
Map<String, String> tipMap = Map.of("erbanNo", String.valueOf(users.getErbanNo()), "cancelDate", String.valueOf(userCancelRecord.getUpdateTime().getTime()),
"nick", userCancelRecord.getNick(), "avatar", userCancelRecord.getAvatar());
throw new OAuthException(BusiStatus.ACCOUNT_CANCEL, tipMap);
}
private DayIpMaxRegisterLimitConfig getIpMaxLimitConfig() {
String config = sysConfService.getSysConfValueById(Constant.SysConfId.IP_MAX_REGISTER_LIMIT_CONFIG);
if (!StringUtils.hasText(config)) {
throw new OAuthException(BusiStatus.ALREADY_NOTEXISTS_CONFIG);
}
return JSON.parseObject(config, DayIpMaxRegisterLimitConfig.class);
}
private RepeatedDeviceIpRegisterLimitConfig getRepeatedDeviceIpLimitConfig() {
String config = sysConfService.getSysConfValueById(Constant.SysConfId.REPEATED_DEVICE_IP_REGISTER_LIMIT_CONFIG);
if (!StringUtils.hasText(config)) {
throw new OAuthException(BusiStatus.ALREADY_NOTEXISTS_CONFIG);
}
return JSON.parseObject(config, RepeatedDeviceIpRegisterLimitConfig.class);
}
}

View File

@@ -0,0 +1,125 @@
package com.accompany.oauth.service;
import com.accompany.common.constant.Constant;
import com.accompany.common.device.DeviceInfo;
import com.accompany.common.status.BusiStatus;
import com.accompany.common.utils.CommonUtil;
import com.accompany.common.utils.DESUtils;
import com.accompany.core.exception.ServiceException;
import com.accompany.core.model.Account;
import com.accompany.core.util.KeyStore;
import com.accompany.core.util.MD5;
import com.accompany.oauth.constant.LoginTypeEnum;
import com.accompany.oauth.exception.OAuthException;
import lombok.SneakyThrows;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* 用户服务 - 用户认证和信息查询
*
* @author Accompany OAuth Team
* @since 1.0.0
*/
@Service
public class AuthenticateService {
@Autowired
private AccountManageService accountManageService;
@Autowired
private AccountLoginService accountLoginService;
/**
* 通过密码认证用户
*
* @param username 手机号
* @param password 密码
* @return 用户详情
* @throws OAuthException 认证失败
*/
@SneakyThrows
public Account authenticateByPassword(String username, String password) {
username = DESUtils.DESAndBase64Decrypt(username, KeyStore.DES_ENCRYPT_KEY);
password = DESUtils.DESAndBase64Decrypt(password, KeyStore.DES_ENCRYPT_KEY);
password = MD5.getMD5(password);
Account account = accountManageService.getAccountPyUsername(username, password);
if (account == null) {
throw new OAuthException(BusiStatus.USER_NOT_EXISTED);
}
accountLoginService.validPwd(username, password, account.getPassword());
return account;
}
public Account authenticateByVerifyCode(String phone, String phoneAreaCode, String code, DeviceInfo deviceInfo) {
if (!CommonUtil.checkPhoneFormat(phoneAreaCode, phone)){
throw new ServiceException(BusiStatus.PARAMERROR);
}
Account account = accountManageService.getOrGenAccountByPhone(phone, phoneAreaCode, code, deviceInfo);
//校验验证码
accountLoginService.checkCodeByUserType(account, code, LoginTypeEnum.PHONE);
return account;
}
/**
* 通过邮箱认证用户
*
* @param email 邮箱
* @param code 验证码
* @param deviceInfo
* @return 用户详情
* @throws OAuthException 认证失败
*/
public Account authenticateByEmail(String email, String code, DeviceInfo deviceInfo) {
Account account = accountManageService.getOrGenAccountByEmail(email, code, deviceInfo, deviceInfo.getClientIp());
//校验验证码
accountLoginService.checkCodeByUserType(account, code, LoginTypeEnum.EMAIL);
return account;
}
/**
* 通过OpenID认证用户
*
* @param openId OpenID
* @param type 第三方类型 (1-微信, 2-Apple等)
* @return 用户详情
* @throws OAuthException 认证失败
*/
public Account authenticateByOpenId(Byte type, String openId, String unionId, String idToken, DeviceInfo deviceInfo) {
return accountManageService.getOrGenAccountByOpenid(type, openId, unionId, idToken, deviceInfo);
}
/**
* 根据用户ID获取用户详情
*
* @param uid 用户ID
* @return 用户详情
* @throws OAuthException 用户不存在
*/
public Account getUserByUid(Long uid) {
return accountManageService.getAccountPyUid(uid);
}
/**
* 检查用户状态是否可用
*
* @throws OAuthException 用户不可用
*/
public void checkUserStatus(Account account) {
if (Constant.AccountState.block.equals(account.getState())) {
throw new OAuthException(BusiStatus.ACCOUNT_BLOCK_ERROR);
}
if (Constant.AccountState.cancel.equals(account.getState())) {
throw new OAuthException(BusiStatus.ACCOUNT_CANCEL);
}
}
}

View File

@@ -0,0 +1,136 @@
package com.accompany.oauth.service;
import com.accompany.common.status.BusiStatus;
import com.accompany.core.model.Account;
import com.accompany.oauth.constant.GrantTypeEnum;
import com.accompany.oauth.constant.LoginTypeEnum;
import com.accompany.oauth.dto.AuthResult;
import com.accompany.oauth.exception.OAuthException;
import com.accompany.oauth.token.TokenManager;
import com.accompany.oauth.model.TokenPair;
import com.accompany.common.device.DeviceInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* 认证服务 - 统一认证入口
*
* @author Accompany OAuth Team
* @since 1.0.0
*/
@Service
public class AuthenticationService {
@Autowired
private AuthenticateService authenticateService;
@Autowired
private AccountLoginService accountLoginService;
@Autowired
private TokenManager tokenManager;
/**
* 用户认证 - 简化接口(直接传参)
*
* @param grantType 授权类型/手机号/邮箱
* @param password 密码
* @param code 验证码
* @param deviceInfo 设备信息
* @return 认证结果
* @throws OAuthException 认证失败
*/
public AuthResult authenticate(String grantType,
String username, String password,
String phone, String phoneAreaCode, String email,
String code, DeviceInfo deviceInfo) {
GrantTypeEnum grantTypeEnum = GrantTypeEnum.fromCode(grantType);
// 1. 根据授权类型进行用户认证
LoginTypeEnum loginTypeEnum = null;
Account account = null;
switch (grantTypeEnum) {
case PASSWORD:
loginTypeEnum = LoginTypeEnum.ID;
account = authenticateService.authenticateByPassword(username, password);
break;
case VERIFY_CODE:
loginTypeEnum = LoginTypeEnum.PHONE;
account = authenticateService.authenticateByVerifyCode(phone, phoneAreaCode, code, deviceInfo);
break;
case EMAIL:
loginTypeEnum = LoginTypeEnum.EMAIL;
account = authenticateService.authenticateByEmail(email, code, deviceInfo);
break;
default:
throw new OAuthException(BusiStatus.INVALID_GRANT);
}
// 2. 检查用户状态
authenticateService.checkUserStatus(account);
accountLoginService.login(account, loginTypeEnum, deviceInfo, null);
// 3. 生成Token
TokenPair tokenPair = tokenManager.generateToken(account.getUid());
// 4. 构建认证结果
return buildAuthResult(tokenPair, account);
}
/**
* 第三方认证
*
* @param type 第三方类型
* @param openId OpenID
* @param unionId UnionID
* @param idToken ID Token
* @param deviceInfo 设备信息
* @return 认证结果
* @throws OAuthException 认证失败
*/
public AuthResult authenticateByThirdParty(Byte type, String openId, String unionId, String idToken, DeviceInfo deviceInfo) {
// 1. 第三方认证
Account account = authenticateService.authenticateByOpenId(type, openId, unionId, idToken, deviceInfo);
// 2. 检查用户状态
authenticateService.checkUserStatus(account);
// 4. 生成Token
TokenPair tokenPair = tokenManager.generateToken(account.getUid());
// 5. 构建认证结果
return buildAuthResult(tokenPair, account);
}
/**
* 注销Token
*
* @param token 访问令牌
*/
public void revokeToken(String token) {
tokenManager.revokeToken(token);
}
/**
* 构建认证结果
*
* @param tokenPair Token对
* @return 认证结果
*/
private AuthResult buildAuthResult(TokenPair tokenPair, Account account) {
// 构建认证结果
AuthResult authResult = new AuthResult();
authResult.setAccessToken(tokenPair.getAccessToken());
authResult.setRefreshToken(tokenPair.getRefreshToken());
authResult.setExpiresIn(tokenPair.getExpiresIn());
authResult.setTokenType(tokenPair.getTokenType());
authResult.setScope(tokenPair.getScope());
// 填充兼容字段
authResult.setUid(account.getUid());
authResult.setNetEaseToken(account.getNeteaseToken());
return authResult;
}
}

View File

@@ -0,0 +1,144 @@
package com.accompany.oauth.ticket;
import com.accompany.common.device.DeviceInfo;
import com.accompany.common.redis.RedisKey;
import com.accompany.common.status.BusiStatus;
import com.accompany.core.model.Account;
import com.accompany.core.model.AccountLoginRecord;
import com.accompany.core.service.account.AccountService;
import com.accompany.core.service.account.LoginRecordService;
import com.accompany.core.service.account.UserAppService;
import com.accompany.oauth.constant.LoginTypeEnum;
import com.accompany.oauth.dto.TicketResponseVO;
import com.accompany.oauth.exception.OAuthException;
import com.accompany.oauth.token.TokenManager;
import com.accompany.oauth.model.TokenValidation;
import com.accompany.oauth.service.AccountLoginService;
import com.accompany.oauth.service.AuthenticateService;
import com.accompany.oauth.util.JwtUtil;
import org.redisson.api.RMapCache;
import org.redisson.api.RedissonClient;
import org.redisson.client.codec.StringCodec;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.concurrent.TimeUnit;
/**
* Ticket服务
* 迁移自OAuth2模块的TicketServices简化实现
*
* @author Accompany OAuth Team
* @since 1.0.0
*/
@Service
public class TicketService implements InitializingBean {
@Autowired
private TokenManager tokenManager;
@Autowired
private AuthenticateService authenticateService;
@Autowired
private JwtUtil jwtUtil;
@Autowired
private UserAppService userAppService;
@Autowired
private AccountLoginService accountLoginService;
@Autowired
private LoginRecordService loginRecordService;
@Autowired
private AccountService accountService;
@Autowired
private RedissonClient redissonClient;
private RMapCache<Long, String> ticketCache;
/**
* 签发票据
*
* @param accessToken 访问令牌
* @return 票据响应
*/
public TicketResponseVO issueTicket(String accessToken) {
// 1. 验证访问令牌
TokenValidation validation = tokenManager.validateToken(accessToken);
if (!validation.isValid()) {
throw new OAuthException(BusiStatus.INVALID_TOKEN);
}
// 2. 获取用户信息
Account account = authenticateService.getUserByUid(validation.getUserId());
authenticateService.checkUserStatus(account);
Date expiration = validation.getExpirationTime();
// 4. 创建票据
Ticket ticket = new Ticket(UUID.randomUUID().toString());
ticket.setAccessToken(accessToken);
ticket.setExpiration(expiration);
ticket.setTicketType(Ticket.MULTI_TYPE);
ticket.setScope(validation.getScopes());
// 5. 增强票据(JWT签名)
String ticketValue = jwtUtil.generateTicket(ticket, account.getUid());
ticket.setValue(ticketValue);
// 6. 存储票据
ticketCache.fastPut(account.getUid(), ticketValue, ticket.getExpiresIn(), TimeUnit.SECONDS);
// 7. 构建响应
TicketResponseVO response = new TicketResponseVO();
response.setUid(account.getUid());
List<TicketResponseVO.TicketVO> tickets = new ArrayList<>();
tickets.add(createTicketVo(ticket));
response.setTickets(tickets);
return response;
}
/**
* 创建票据VO
*
* @param ticket 票据
* @return 票据VO
*/
private TicketResponseVO.TicketVO createTicketVo(Ticket ticket) {
TicketResponseVO.TicketVO ticketVo = new TicketResponseVO.TicketVO();
ticketVo.setTicket(ticket.getValue());
ticketVo.setExpiresIn(ticket.getExpiresIn());
return ticketVo;
}
/**
* 保存登录记录(异步)
*
* @param uid 用户ID
* @param ipAddress IP地址
* @param deviceInfo 设备信息
*/
public void saveLoginRecord(Long uid, String ipAddress, DeviceInfo deviceInfo) {
Optional.ofNullable(uid).ifPresent(id -> {
userAppService.updateCurrentApp(uid, deviceInfo.getApp(), new Date(), ipAddress, deviceInfo.getAppVersion());
long count = loginRecordService.countLoginRecordToday(id);
if (count <= 0L) {
Account account = accountService.getAccountByUid(id);
Optional.ofNullable(account).ifPresent(acc -> {
AccountLoginRecord record = accountLoginService.buildAccountLoginRecord(ipAddress, acc, LoginTypeEnum.TICKET.getValue(), deviceInfo, null);
loginRecordService.addAccountLoginRecord(record);
});
}
});
}
@Override
public void afterPropertiesSet() {
ticketCache = redissonClient.getMapCache(RedisKey.uid_ticket.getKey(), StringCodec.INSTANCE);
}
}

View File

@@ -0,0 +1,99 @@
package com.accompany.oauth.token;
import com.accompany.common.redis.RedisKey;
import com.accompany.common.status.BusiStatus;
import com.accompany.core.util.StringUtils;
import com.accompany.oauth.exception.OAuthException;
import com.accompany.oauth.model.TokenPair;
import com.accompany.oauth.model.TokenValidation;
import com.accompany.oauth.util.JwtUtil;
import io.jsonwebtoken.Claims;
import org.redisson.api.RMapCache;
import org.redisson.api.RedissonClient;
import org.redisson.client.codec.StringCodec;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.concurrent.TimeUnit;
/**
* Token管理器 - 使用Redisson进行Token存储和管理
*
* @author Accompany OAuth Team
* @since 1.0.0
*/
@Component
public class TokenManager implements InitializingBean {
@Autowired
public JwtUtil jwtUtil;
@Autowired
private RedissonClient redissonClient;
private RMapCache<Long, String> tokenCache;
private final String defaultScope = "read write";
public TokenPair generateToken(Long uid) {
// 生成JWT token
Date now = new Date();
String accessToken = jwtUtil.generateAccessToken(uid, now);
String refreshToken = jwtUtil.generateRefreshToken(uid, now);
// 存储access token
tokenCache.fastPut(uid, accessToken, jwtUtil.getAccessTokenExpiration(), TimeUnit.SECONDS);
TokenPair tokenPair = new TokenPair();
tokenPair.setAccessToken(accessToken);
tokenPair.setRefreshToken(refreshToken);
tokenPair.setExpiresIn(jwtUtil.getAccessTokenExpiration());
tokenPair.setTokenType("Bearer");
tokenPair.setScope(defaultScope);
return tokenPair;
}
/**
* 验证Token
*
* @param token 访问令牌
* @return 验证结果
*/
public TokenValidation validateToken(String token) {
// 首先验证JWT格式和签名
Claims claims = jwtUtil.validateAndParseToken(token);
// 提取token信息
Long uid = Long.valueOf(claims.getSubject());
// 检查Redis中是否存在该token
String cacheToken = tokenCache.get(uid);
if (StringUtils.isBlank(cacheToken) || !cacheToken.equals(token)) {
throw new OAuthException(BusiStatus.INVALID_TOKEN);
}
String clientId = claims.get("client_id", String.class);
Date expirationTime = claims.getExpiration();
return TokenValidation.valid(uid, expirationTime, clientId);
}
/**
* 撤销Token
*
* @param token 访问令牌
*/
public void revokeToken(String token) {
Claims claims = jwtUtil.validateAndParseToken(token);
Long uid = Long.valueOf(claims.getSubject());
// 删除access token
tokenCache.fastRemove(uid);
}
@Override
public void afterPropertiesSet() {
tokenCache = redissonClient.getMapCache(RedisKey.uid_access_token.getKey(), StringCodec.INSTANCE);
}
}

View File

@@ -0,0 +1,137 @@
package com.accompany.oauth.util;
import com.accompany.common.status.BusiStatus;
import com.accompany.common.utils.UUIDUtil;
import com.accompany.oauth.config.OAuthConfig;
import com.accompany.oauth.exception.OAuthException;
import com.accompany.oauth.ticket.Ticket;
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import lombok.Getter;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.crypto.SecretKey;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* JWT工具类 - 使用更安全的实现
*
* @author Accompany OAuth Team
* @since 1.0.0
*/
@Component
public class JwtUtil implements InitializingBean {
@Autowired
private OAuthConfig oAuthConfig;
@Getter
private long accessTokenExpiration = 2592000;
@Getter
private long refreshTokenExpiration = 3196800;
private SecretKey secretKey;
/**
* 生成访问令牌 (兼容OAuth2格式)
*
* @param userId 用户ID
* @param now
* @return JWT令牌
*/
public String generateAccessToken(Long userId, Date now) {
Date expiration = getExpiration(now);
JwtBuilder builder = Jwts.builder()
.setSubject(userId.toString())
.setIssuedAt(now)
.setExpiration(expiration)
.claim("client_id", oAuthConfig.getClientId())
.claim("token_type", "access_token")
.claim("uid", userId) // 兼容OAuth2
.claim("user_name", userId.toString()) // 兼容OAuth2
.claim("authorities", "oauth2") // 兼容OAuth2
.claim("jti", UUIDUtil.get()) // 兼容OAuth2
.claim("scope", "read write")
.signWith(secretKey, SignatureAlgorithm.HS256);
return builder.compact();
}
public Date getExpiration(Date now){
return new Date(now.getTime() + accessTokenExpiration * 1000);
}
/**
* 生成刷新令牌
*
* @param userId 用户ID
* @param now
* @return JWT令牌
*/
public String generateRefreshToken(Long userId, Date now) {
Date expiration = new Date(now.getTime() + refreshTokenExpiration * 1000);
return Jwts.builder()
.setSubject(userId.toString())
.setIssuedAt(now)
.setExpiration(expiration)
.claim("client_id", oAuthConfig.getClientId())
.claim("token_type", "refresh_token")
.signWith(secretKey, SignatureAlgorithm.HS256)
.compact();
}
public String generateTicket(Ticket ticket, Long uid){
Map<String, Object> claims = new HashMap<>();
claims.put("ticket_id", ticket.getValue());
claims.put("client_id", oAuthConfig.getClientId());
claims.put("exp", ticket.getExpiresIn());
claims.put("uid", uid);
claims.put("ticket_type", ticket.getTicketType());
claims.put("scope", "read write");
Date now = new Date();
Date expiration = ticket.getExpiration();
JwtBuilder builder = Jwts.builder()
.setClaims(claims)
.setIssuedAt(now)
.setExpiration(expiration)
.signWith(secretKey, SignatureAlgorithm.HS256);
return builder.compact();
}
/**
* 验证并解析JWT令牌
*
* @param token JWT令牌
* @return Claims对象
* @throws OAuthException 令牌无效或过期
*/
public Claims validateAndParseToken(String token) {
try {
return Jwts.parser()
.setSigningKey(secretKey)
.build()
.parseClaimsJws(token)
.getBody();
} catch (ExpiredJwtException e) {
throw new OAuthException(BusiStatus.ACCESS_TOKEN_HAS_EXPIRED);
} catch (JwtException e) {
throw new OAuthException(BusiStatus.INVALID_TOKEN);
}
}
@Override
public void afterPropertiesSet() {
// 确保密钥长度足够
this.secretKey = Keys.hmacShaKeyFor(oAuthConfig.getJwtSignKey().getBytes(StandardCharsets.UTF_8));
}
}

View File

@@ -0,0 +1,45 @@
<?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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.accompany</groupId>
<artifactId>accompany-oauth</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>accompany-oauth-web</artifactId>
<packaging>jar</packaging>
<description>OAuth Web模块 - 控制器和Web配置</description>
<dependencies>
<dependency>
<groupId>com.accompany</groupId>
<artifactId>accompany-oauth-service</artifactId>
<version>${revision}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>com.accompany.oauth.OAuthApplication</mainClass>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
<configuration>
<classifier>exec</classifier>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,47 @@
package com.accompany.oauth;
import org.dromara.dynamictp.spring.annotation.EnableDynamicTp;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
/**
* OAuth应用程序启动类
*
* @author Accompany OAuth Team
* @since 1.0.0
*/
@EnableDynamicTp
@SpringBootApplication
@ComponentScan("com.accompany")
@ComponentScan(basePackages = {
"com.accompany.oauth", // OAuth模块
"com.accompany.common", // 公共模块
"com.accompany.core" // 核心模块
})
@MapperScan({"com.accompany.*.mapper","com.accompany.*.mybatismapper"})
public class OAuthApplication {
public static void main(String[] args) {
// 设置系统属性
System.setProperty("spring.application.name", "accompany-oauth");
SpringApplication app = new SpringApplication(OAuthApplication.class);
// 添加启动横幅
app.setBanner((environment, sourceClass, out) -> {
out.println();
out.println(" ____ ");
out.println(" / __ \\ ___ __ __ ___ __ __ ___ ___ ");
out.println("/ /_/ / / / / V / / / / V / / / / / ");
out.println("\\____/ /__/ /_/\\_/ /__/ /_/\\_/ /__/ /__/ ");
out.println();
out.println(":: Accompany OAuth Service :: (v1.0.0)");
out.println(":: Powered by Spring Boot :: ");
out.println();
});
app.run(args);
}
}

View File

@@ -0,0 +1,61 @@
package com.accompany.oauth.config;
import com.accompany.oauth.dto.AuthResult;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import org.springframework.boot.jackson.JsonComponent;
import java.io.IOException;
/**
* AuthResult自定义序列化器 - 直接输出OAuth2标准格式
* 输出格式:{uid, access_token, token_type, expires_in, netEaseToken, userToken, loginKey, ...}
* 直接兼容OAuth2的CustomOAuth2AccessToken格式
*
* @author Accompany OAuth Team
* @since 1.0.0
*/
@JsonComponent
public class AuthResultJsonSerializer extends JsonSerializer<AuthResult> {
@Override
public void serialize(AuthResult authResult, JsonGenerator gen, SerializerProvider serializers)
throws IOException {
gen.writeStartObject();
// 标准OAuth字段
gen.writeStringField("access_token", authResult.getAccessToken());
gen.writeStringField("token_type", authResult.getTokenType());
if (authResult.getRefreshToken() != null) {
gen.writeStringField("refresh_token", authResult.getRefreshToken());
}
if (authResult.getExpiresIn() != null) {
gen.writeNumberField("expires_in", authResult.getExpiresIn());
}
if (authResult.getScope() != null) {
gen.writeStringField("scope", authResult.getScope());
}
// 用户ID (兼容oauth2)
if (authResult.getUid() != null) {
gen.writeNumberField("uid", authResult.getUid());
}
// 网易云信Token (兼容oauth2)
if (authResult.getNetEaseToken() != null && !authResult.getNetEaseToken().isEmpty()) {
gen.writeStringField("netEaseToken", authResult.getNetEaseToken());
}
// accid (兼容oauth2)
if (authResult.getUid() != null) {
gen.writeStringField("accid", authResult.getUid().toString());
}
gen.writeEndObject();
}
}

View File

@@ -0,0 +1,99 @@
package com.accompany.oauth.config;
import cn.hutool.core.util.StrUtil;
import com.accompany.common.status.BusiStatus;
import com.accompany.core.base.SpringContextHolder;
import com.accompany.oauth.exception.OAuthException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
/**
* 全局异常处理器
*
* @author Accompany OAuth Team
* @since 1.0.0
*/
@Slf4j
@ControllerAdvice
public class OauthGlobalExceptionHandler {
/**
* 处理OAuth异常
*
* @param e OAuth异常
* @return 错误响应
*/
@ExceptionHandler(OAuthException.class)
@ResponseBody
public ResponseEntity<Map<String, Object>> handleOAuthException(OAuthException e) {
BusiStatus status = e.getBusiStatus();
Map<String, Object> map = new HashMap<>();
map.put("code", status.getCode());
Locale locale = LocaleContextHolder.getLocale();
String i18nId = BusiStatus.class.getSimpleName() + StrUtil.DOT + status.getName();
String abc = SpringContextHolder.getBean(MessageSource.class).getMessage(i18nId, null, status.getMessage(), locale);
map.put("message", abc);
Map<String, String> additionalInformation = e.getAdditionalInformation();
if (CollectionUtils.isEmpty(additionalInformation)){
return ResponseEntity.status(status.getCode()).body(map);
}
for (String key : additionalInformation.keySet()){
map.put(key, additionalInformation.get(key));
}
return ResponseEntity.status(status.getCode()).body(map);
}
/**
* 处理参数异常
*
* @param e 参数异常
* @return 错误响应
*/
@ExceptionHandler(IllegalArgumentException.class)
@ResponseBody
public ResponseEntity<Map<String, Object>> handleIllegalArgumentException(IllegalArgumentException e) {
log.warn("参数异常: {}", e.getMessage());
Map<String, Object> response = new HashMap<>();
response.put("error", "invalid_request");
response.put("error_description", e.getMessage());
response.put("timestamp", System.currentTimeMillis());
return ResponseEntity.badRequest().body(response);
}
/**
* 处理其他异常
*
* @param e 异常
* @return 错误响应
*/
@ExceptionHandler(Exception.class)
@ResponseBody
public ResponseEntity<Map<String, Object>> handleException(Exception e) {
log.error("系统异常", e);
Map<String, Object> response = new HashMap<>();
response.put("error", "server_error");
response.put("error_description", "系统内部错误");
response.put("timestamp", System.currentTimeMillis());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
}
}

View File

@@ -0,0 +1,28 @@
package com.accompany.oauth.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* Web配置类
*
* @author Accompany OAuth Team
* @since 1.0.0
*/
@Configuration
public class WebConfig implements WebMvcConfigurer {
/**
* 配置跨域
*/
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOriginPatterns("*")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(true)
.maxAge(3600);
}
}

View File

@@ -0,0 +1,65 @@
package com.accompany.oauth.controller;
import com.accompany.common.device.DeviceInfo;
import com.accompany.common.result.BusiResult;
import com.accompany.core.base.DeviceInfoContextHolder;
import com.accompany.oauth.dto.AuthResult;
import com.accompany.oauth.service.AuthenticationService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* 用户账户控制器
*
* @author Accompany OAuth Team
* @since 1.0.0
*/
@Slf4j
@RestController
@RequestMapping("/acc")
public class AccountController {
@Autowired
private AuthenticationService authenticationService;
/**
* 第三方登录 (兼容OAuth2格式)
*
* @param openId OpenID
* @param type 第三方类型
* @param unionId UnionID(可选)
* @param idToken ID Token(可选)
* @return 直接返回AuthResult结构
*/
@RequestMapping("/third/login")
public BusiResult<AuthResult> thirdLogin(
Byte type,
@RequestParam("openid") String openId,
@RequestParam("openid")String unionId,
String idToken) {
log.info("/acc/third/login? app {} , type {}, unionId {}", type, unionId);
DeviceInfo deviceInfo = DeviceInfoContextHolder.get();
AuthResult authResult = authenticationService.authenticateByThirdParty(type, openId, unionId, idToken, deviceInfo);
return BusiResult.success(authResult);
}
/**
* 用户注销 (兼容OAuth2格式)
*
* @param accessToken 访问令牌
* @return BusiResult响应结果
*/
@PostMapping("/logout")
public BusiResult<Void> logout(@RequestParam("access_token") String accessToken) {
if (StringUtils.hasText(accessToken)) {
authenticationService.revokeToken(accessToken);
}
return BusiResult.success();
}
}

View File

@@ -0,0 +1,99 @@
package com.accompany.oauth.controller;
import com.accompany.common.result.BusiResult;
import com.accompany.common.status.BusiStatus;
import com.accompany.common.utils.IPUtils;
import com.accompany.core.base.DeviceInfoContextHolder;
import com.accompany.core.exception.ServiceException;
import com.accompany.oauth.dto.AuthResult;
import com.accompany.oauth.dto.TicketResponseVO;
import com.accompany.common.device.DeviceInfo;
import com.accompany.oauth.service.AuthenticationService;
import com.accompany.oauth.ticket.Ticket;
import com.accompany.oauth.ticket.TicketService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
/**
* OAuth认证控制器
*
* @author Accompany OAuth Team
* @since 1.0.0
*/
@RestController
@RequestMapping("/oauth")
public class OAuthController {
@Autowired
private AuthenticationService authenticationService;
@Autowired
private TicketService ticketService;
/**
* Token获取接口 - 简化实现 (兼容OAuth2格式)
* 支持表单提交和JSON提交两种方式
*
* @param grantType 授权类型 (password, verify_code, email等)
* @param password 密码
* @param code 验证码
* @return 直接返回AuthResult结构兼容OAuth2的CustomOAuth2AccessToken
*/
@PostMapping("/token")
public AuthResult token(@RequestParam(value = "grant_type") String grantType,
@RequestParam(value = "username") String username,
@RequestParam(value = "password") String password,
@RequestParam(value = "phone") String phone,
@RequestParam(value = "phoneAreaCode") String phoneAreaCode,
@RequestParam(value = "email") String email,
@RequestParam(value = "code") String code) {
// 1. 简单验证
if (!StringUtils.hasText(grantType)) {
throw new ServiceException(BusiStatus.PARAMERROR);
}
// 2. 获取设备信息
DeviceInfo deviceInfo = DeviceInfoContextHolder.get();
// 3. 直接调用服务,不做中间转换
return authenticationService.authenticate(grantType,
username, password, phone, phoneAreaCode, email, code, deviceInfo);
}
/**
* Ticket签发接口 - 简化实现 (兼容OAuth2格式)
*
* @param issueType 签发类型 (once/multi)
* @param accessToken 访问令牌
* @param httpRequest HTTP请求
* @return BusiResult包装的Ticket响应
*/
@GetMapping("/ticket")
public BusiResult<TicketResponseVO> issueTicket(@RequestParam("issue_type") String issueType,
@RequestParam("access_token") String accessToken,
HttpServletRequest httpRequest) {
// 验证签发类型
if (!Ticket.ONCE_TYPE.equals(issueType) && !Ticket.MULTI_TYPE.equals(issueType)) {
throw new IllegalArgumentException("不支持的票据签发类型");
}
DeviceInfo deviceInfo = DeviceInfoContextHolder.get();
// 直接传递accessToken给TicketService
TicketResponseVO result = ticketService.issueTicket(accessToken);
// 获取IP地址并异步记录用户登录信息
String ipAddress = IPUtils.getRealIpAddress(httpRequest);
Long uid = result.getUid();
ticketService.saveLoginRecord(uid, ipAddress, deviceInfo);
return BusiResult.success(result);
}
}

View File

@@ -0,0 +1,149 @@
package com.accompany.oauth.controller;
import com.accompany.common.annotation.Authorization;
import com.accompany.common.result.BusiResult;
import com.accompany.common.status.BusiStatus;
import com.accompany.common.utils.DESUtils;
import com.accompany.core.base.UidContextHolder;
import com.accompany.core.exception.ServiceException;
import com.accompany.core.model.Account;
import com.accompany.core.service.account.AccountService;
import com.accompany.core.util.KeyStore;
import com.accompany.oauth.service.AccountManageService;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
@Slf4j
@RestController
@RequestMapping("/acc/pwd")
public class PwdController {
/** 密码强度检查正则必须包括大小写字母和数字长度为6到16 */
private static final String PASSWORD_REGIX_V2 = "^(?=.*\\d)(?=.*[a-zA-Z]).{6,16}$";
@Autowired
private AccountService accountService;
@Autowired
private AccountManageService accountManageService;
/**
* 重置密码接口,用于用户忘记密码,找回密码服务
*
* @param newPwd
* 新密码
* @param smsCode
* 重置码
* @return 1:成功 2重置码无效 3不存在该用户 4其它错误
*/
@PostMapping("/reset")
@SneakyThrows
public BusiResult<Void> resetPassword(String phone, String newPwd, String smsCode) {
if (StringUtils.isBlank(phone) || StringUtils.isBlank(newPwd) || StringUtils.isBlank(smsCode)){
throw new ServiceException(BusiStatus.PARAMERROR);
}
newPwd = DESUtils.DESAndBase64Decrypt(newPwd, KeyStore.DES_ENCRYPT_KEY);
// 密码长度检查
if(!newPwd.matches(PASSWORD_REGIX_V2)){
return new BusiResult<>(BusiStatus.WEAK_PASSWORD);
}
Long uid = UidContextHolder.get();
phone = DESUtils.DESAndBase64Decrypt(phone, KeyStore.DES_ENCRYPT_KEY);
accountManageService.resetPasswordByResetCode(uid, phone, newPwd, smsCode);
return new BusiResult<>(BusiStatus.SUCCESS);
}
/**
* 重置密码接口,用于用户忘记密码,找回密码服务
*
* @param newPwd
* 新密码
* @param email
* 邮箱
* @return 1:成功 2重置码无效 3不存在该用户 4其它错误
*/
@PostMapping("/resetByEmail")
@SneakyThrows
public BusiResult<Void> resetPasswordByEmail(String email, String newPwd, String code) {
if (StringUtils.isBlank(email) || StringUtils.isBlank(newPwd) || StringUtils.isBlank(code)){
throw new ServiceException(BusiStatus.PARAMERROR);
}
Long uid = UidContextHolder.get();
email = DESUtils.DESAndBase64Decrypt(email, KeyStore.DES_ENCRYPT_KEY);
newPwd = DESUtils.DESAndBase64Decrypt(newPwd, KeyStore.DES_ENCRYPT_KEY);
// 密码长度检查
if(!newPwd.matches(PASSWORD_REGIX_V2)){
return new BusiResult<>(BusiStatus.WEAK_PASSWORD);
}
accountManageService.resetPasswordByEmailCode(uid, email, newPwd, code);
return new BusiResult<>(BusiStatus.SUCCESS);
}
/**
* 设置新密码
* @param newPwd
* @return
*/
@Authorization
@PostMapping("/set")
@SneakyThrows
public BusiResult<Void> setupPassword(String newPwd) {
//加入密码DES解密
newPwd = DESUtils.DESAndBase64Decrypt(newPwd, KeyStore.DES_ENCRYPT_KEY);
// 密码长度检查
if(!newPwd.matches(PASSWORD_REGIX_V2)){
return new BusiResult<>(BusiStatus.WEAK_PASSWORD);
}
Long uid = UidContextHolder.get();
accountManageService.setupInitialPassword(uid, newPwd);
return new BusiResult<>(BusiStatus.SUCCESS);
}
@Authorization
@PostMapping("/modify")
@SneakyThrows
public BusiResult<Void> modifyPassword(HttpServletRequest request,
String pwd, String newPwd) {
newPwd = DESUtils.DESAndBase64Decrypt(newPwd, KeyStore.DES_ENCRYPT_KEY);
// 密码长度检查
if(!newPwd.matches(PASSWORD_REGIX_V2)){
return new BusiResult<>(BusiStatus.WEAK_PASSWORD);
}
Long uid = UidContextHolder.get();
// 加入密码DES解密
pwd = DESUtils.DESAndBase64Decrypt(pwd, KeyStore.DES_ENCRYPT_KEY);
Account account = this.accountService.getById(uid);
if (account == null) {
return new BusiResult<>(BusiStatus.INVALID_USER);
}
accountManageService.resetPasswordByOldPassword(account.getPhone(), pwd, newPwd);
return new BusiResult<>(BusiStatus.SUCCESS);
}
}

View File

@@ -0,0 +1,64 @@
#MySQL数据库配置
spring:
dynamic-datasource:
master:
poolName: master
jdbcUrl: jdbc:mysql://124.156.164.187:3306/peko?autoReconnect=true&useUnicode=true&characterEncoding=UTF-8&tinyInt1isBit=false&useSSL=false&useCursorFetch=true
username: root
password: anan@dev##
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.zaxxer.hikari.HikariDataSource
hikari:
minimum-idle: 10
maximum-pool-size: 20
connection-test-query: select 1
max-lifetime: 7000
slave:
poolName: slave
jdbcUrl: jdbc:mysql://124.156.164.187:3306/peko?autoReconnect=true&useUnicode=true&characterEncoding=UTF-8&tinyInt1isBit=false&useSSL=false&useCursorFetch=true
username: root
password: anan@dev##
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.zaxxer.hikari.HikariDataSource
hikari:
minimum-idle: 10
maximum-pool-size: 20
connection-test-query: select 1
max-lifetime: 7000
redis:
host: 124.156.164.187
port: 6200
maxTotal: 100
maxIdle: 50
maxWait: 2500
testOnBorrow: true
testOnReturn: true
password: anan@dev@redis@#!
redisson:
# file: classpath:redisson.yaml
config: |
singleServerConfig:
address: redis://124.156.164.187:6200
password: anan@dev@redis@#!
connectionMinimumIdleSize: 4
timeout: 10000
threads: 8
nettyThreads: 16
codec: !<org.redisson.codec.JsonJacksonCodec> {}
transportMode: "NIO"
## rocketmq 配置
rocketmq:
name-server: 124.156.164.187:9876
producer:
group: peko-group
sendMessageTimeout: 300000
server:
port: 8081
oauth2:
server:
clientId: erban-client
clientSecret: uyzjdhds
jwtSignKey: ead18800082c5807806d8f54914f84f4fa360dd568f4784288a4439b5fee0f25

View File

@@ -0,0 +1,83 @@
spring:
application:
name: oauth
profiles:
active: native
main:
allow-bean-definition-overriding : true
allow-circular-references: true
mvc:
pathmatch:
matching-strategy: ant_path_matcher
---
spring:
config:
activate:
on-profile: native
cloud:
nacos:
config:
server-addr: 124.156.164.187:8848
namespace: 5edc3246-3e69-4be5-a32a-273f0a09ddf6
file-extension: yml
shared-configs:
- data-id: application.yml
refresh: true
- data-id: thirdpart.yml
refresh: true
- data-id: pay.yml
refresh: true
- data-id: sysconf.yml
refresh: true
- data-id: dtp.yml
refresh: true
---
spring:
config:
activate:
on-profile: dev
cloud:
nacos:
config:
server-addr: 124.156.164.187:8848
namespace: 5edc3246-3e69-4be5-a32a-273f0a09ddf6
file-extension: yml
shared-configs:
- data-id: application.yml
refresh: true
- data-id: thirdpart.yml
refresh: true
- data-id: pay.yml
refresh: true
- data-id: sysconf.yml
refresh: true
- data-id: dtp.yml
refresh: true
- data-id: database.yml
refresh: true
---
spring:
config:
activate:
on-profile: prod
cloud:
nacos:
config:
server-addr: 172.19.16.15:8848
namespace: 0c9bf047-19ca-4969-bf5d-bbc1f86b501a
file-extension: yml
shared-configs:
- data-id: application.yml
refresh: true
- data-id: thirdpart.yml
refresh: true
- data-id: pay.yml
refresh: true
- data-id: sysconf.yml
refresh: true
- data-id: threadpool.yml
refresh: true
- data-id: database.yml
refresh: true

View File

@@ -0,0 +1,133 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds">
<!--引入默认的一些设置-->
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
<!--web信息-->
<logger name="org.springframework.web" level="info"/>
<!--定义日志文件的存储地址 勿在 LogBack 的配置中使用相对路径-->
<property name="LOG_HOME" value="/data/java/weblog/accompany-oauth"/>
<property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] [%X{trace_uuid}] %-5level %logger{36} %line - %msg%n" />
<!--写入日志到控制台的appender,用默认的,但是要去掉charset,否则windows下tomcat下乱码-->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${LOG_PATTERN}</pattern>
</encoder>
</appender>
<appender name="info_file" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_HOME}/web_info.log</file>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>INFO</level>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_HOME}/rolling/web_info.%d{yyyy-MM-dd}.%i.log.gz
</fileNamePattern>
<maxHistory>90</maxHistory>
<cleanHistoryOnStart>true</cleanHistoryOnStart>
<timeBasedFileNamingAndTriggeringPolicy
class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>256MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<encoder>
<charset>UTF-8</charset>
<pattern>${LOG_PATTERN}</pattern>
</encoder>
</appender>
<appender name="warn_file" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_HOME}/web_warn.log</file>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>WARN</level>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_HOME}/rolling/web_warn_.%d{yyyy-MM-dd}.%i.log.gz
</fileNamePattern>
<maxHistory>90</maxHistory>
<cleanHistoryOnStart>true</cleanHistoryOnStart>
<timeBasedFileNamingAndTriggeringPolicy
class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>256MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<encoder>
<charset>UTF-8</charset>
<pattern>${LOG_PATTERN}</pattern>
</encoder>
</appender>
<appender name="error_file" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_HOME}/web_error.log</file>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERROR</level>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_HOME}/rolling/web_error_.%d{yyyy-MM-dd}.%i.log.gz
</fileNamePattern>
<maxHistory>90</maxHistory>
<cleanHistoryOnStart>true</cleanHistoryOnStart>
<timeBasedFileNamingAndTriggeringPolicy
class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>256MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<encoder>
<charset>UTF-8</charset>
<pattern>${LOG_PATTERN}</pattern>
</encoder>
</appender>
<!--异步到文件-->
<appender name="info_async_file" class="ch.qos.logback.classic.AsyncAppender">
<discardingThreshold>0</discardingThreshold>
<queueSize>512</queueSize>
<appender-ref ref="info_file"/>
</appender>
<appender name ="warn_async_file" class= "ch.qos.logback.classic.AsyncAppender">
<discardingThreshold>0</discardingThreshold>
<queueSize>512</queueSize>
<includeCallerData>false</includeCallerData>
<appender-ref ref ="warn_file"/>
</appender>
<appender name ="error_async_file" class= "ch.qos.logback.classic.AsyncAppender">
<discardingThreshold>0</discardingThreshold>
<queueSize>512</queueSize>
<includeCallerData>false</includeCallerData>
<appender-ref ref ="error_file"/>
</appender>
<!--生产环境:打印控制台和输出到文件-->
<springProfile name="prod">
<root level="info">
<appender-ref ref="info_async_file"/>
<appender-ref ref="warn_async_file"/>
<appender-ref ref="error_async_file"/>
</root>
</springProfile>
<!--开发环境:打印控制台-->
<springProfile name="dev">
<logger name="com.accompany" level="DEBUG"/>
<root level="info">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="info_async_file"/>
<appender-ref ref="warn_async_file"/>
<appender-ref ref="error_async_file"/>
</root>
</springProfile>
<springProfile name="native">
<logger name="com.accompany" level="DEBUG"/>
<root level="info">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="info_async_file"/>
<appender-ref ref="warn_async_file"/>
<appender-ref ref="error_async_file"/>
</root>
</springProfile>
</configuration>

View File

@@ -0,0 +1,155 @@
package com.accompany.oauth.integration;
import com.accompany.oauth.dto.AuthResult;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import static org.junit.jupiter.api.Assertions.*;
/**
* OAuth2兼容性集成测试
* 验证OAuth模块与OAuth2模块的API兼容性
*
* @author Accompany OAuth Team
* @since 1.0.0
*/
@SpringBootTest
@AutoConfigureMockMvc
public class OAuth2CompatibilityTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper;
/**
* 测试密码认证端点兼容性 (表单格式)
*/
@Test
public void testPasswordAuthenticationForm() throws Exception {
MvcResult result = mockMvc.perform(post("/oauth/token")
.param("grant_type", "password")
.param("phone", "13800138000")
.param("password", "123456")
.param("client_id", "erban-client")
.contentType(MediaType.APPLICATION_FORM_URLENCODED))
.andExpect(status().isOk())
.andExpect(jsonPath("$.access_token").exists())
.andExpect(jsonPath("$.token_type").value("Bearer"))
.andExpect(jsonPath("$.expires_in").exists())
.andExpect(jsonPath("$.uid").exists())
.andReturn();
String responseJson = result.getResponse().getContentAsString();
AuthResult authResult = objectMapper.readValue(responseJson, AuthResult.class);
// 验证OAuth2兼容字段
assertNotNull(authResult.getAccessToken());
assertEquals("Bearer", authResult.getTokenType());
assertNotNull(authResult.getExpiresIn());
assertNotNull(authResult.getUid());
}
/**
* 测试验证码认证端点兼容性
*/
@Test
public void testVerifyCodeAuthentication() throws Exception {
mockMvc.perform(post("/oauth/token")
.param("grant_type", "verify_code")
.param("phone", "13800138000")
.param("code", "888888")
.param("client_id", "erban-client")
.contentType(MediaType.APPLICATION_FORM_URLENCODED))
.andExpect(status().isOk())
.andExpect(jsonPath("$.access_token").exists())
.andExpect(jsonPath("$.token_type").value("Bearer"))
.andExpect(jsonPath("$.uid").exists());
}
/**
* 测试邮箱认证端点兼容性
*/
@Test
public void testEmailAuthentication() throws Exception {
mockMvc.perform(post("/oauth/token")
.param("grant_type", "email")
.param("email", "test@example.com")
.param("code", "666666")
.param("client_id", "erban-client")
.contentType(MediaType.APPLICATION_FORM_URLENCODED))
.andExpect(status().isOk())
.andExpect(jsonPath("$.access_token").exists())
.andExpect(jsonPath("$.token_type").value("Bearer"))
.andExpect(jsonPath("$.uid").exists());
}
/**
* 测试Token响应格式兼容性
* 确保返回的JSON结构与OAuth2的CustomOAuth2AccessToken一致
*/
@Test
public void testTokenResponseFormat() throws Exception {
MvcResult result = mockMvc.perform(post("/oauth/token")
.param("grant_type", "password")
.param("phone", "13800138000")
.param("password", "123456")
.param("client_id", "erban-client")
.contentType(MediaType.APPLICATION_FORM_URLENCODED))
.andExpect(status().isOk())
.andReturn();
String responseJson = result.getResponse().getContentAsString();
// 验证JSON包含所有OAuth2字段
assertTrue(responseJson.contains("access_token"));
assertTrue(responseJson.contains("token_type"));
assertTrue(responseJson.contains("expires_in"));
assertTrue(responseJson.contains("scope"));
assertTrue(responseJson.contains("uid"));
assertTrue(responseJson.contains("netEaseToken"));
// 验证结构与OAuth2兼容
AuthResult authResult = objectMapper.readValue(responseJson, AuthResult.class);
assertNotNull(authResult.getAccessToken());
assertEquals("Bearer", authResult.getTokenType());
assertNotNull(authResult.getUid());
assertNotNull(authResult.getNetEaseToken());
}
/**
* 测试用户注销端点兼容性
*/
@Test
public void testLogoutCompatibility() throws Exception {
// 先获取token
MvcResult loginResult = mockMvc.perform(post("/oauth/token")
.param("grant_type", "password")
.param("phone", "13800138000")
.param("password", "123456")
.param("client_id", "erban-client")
.contentType(MediaType.APPLICATION_FORM_URLENCODED))
.andExpect(status().isOk())
.andReturn();
String responseJson = loginResult.getResponse().getContentAsString();
AuthResult authResult = objectMapper.readValue(responseJson, AuthResult.class);
// 测试注销
mockMvc.perform(post("/acc/logout")
.param("access_token", authResult.getAccessToken())
.contentType(MediaType.APPLICATION_FORM_URLENCODED))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value(200))
.andExpect(jsonPath("$.message").value("success"));
}
}

21
accompany-oauth/pom.xml Normal file
View File

@@ -0,0 +1,21 @@
<?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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.accompany</groupId>
<artifactId>accompany-dependencies</artifactId>
<version>1.0.0</version>
<relativePath>../accompany-dependencies</relativePath>
</parent>
<artifactId>accompany-oauth</artifactId>
<packaging>pom</packaging>
<modules>
<module>accompany-oauth-sdk</module>
<module>accompany-oauth-service</module>
<module>accompany-oauth-web</module>
</modules>
</project>

View File

@@ -62,13 +62,12 @@ public class JwtTicketConverter implements TicketEnhancer,TicketCoverter {
protected String encode(Ticket ticket, OAuth2Authentication authentication, AccountDetails userDetails) {
String content;
try {
content = objectMapper.writeValueAsString(convertTicket(ticket, authentication,userDetails));
content = objectMapper.writeValueAsString(convertTicket(ticket, authentication, userDetails));
}
catch (Exception e) {
throw new IllegalStateException("Cannot convert access token to JSON", e);
}
String token = JwtHelper.encode(content, signer).getEncoded();
return token;
return JwtHelper.encode(content, signer).getEncoded();
}
}

View File

@@ -3,7 +3,6 @@ package com.accompany.oauth2.jwt;
import com.accompany.common.redis.RedisKey;
import com.accompany.core.service.common.JedisService;
import com.accompany.oauth2.model.AccountDetails;
import com.accompany.oauth2.service.account.AccountH5LoginService;
import com.accompany.oauth2.token.CustomOAuth2AccessToken;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
@@ -18,9 +17,6 @@ public class JwtTokenConverter extends JwtAccessTokenConverter {
@Autowired
private JedisService jedisService;
@Autowired
private AccountH5LoginService accountH5LoginService;
@Override
public CustomOAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
accessToken = super.enhance(accessToken, authentication);

View File

@@ -1,107 +0,0 @@
package com.accompany.oauth2.service.account;
import com.accompany.common.redis.RedisKey;
import com.accompany.common.status.BusiStatus;
import com.accompany.core.base.SpringContextHolder;
import com.accompany.core.enumeration.PartitionEnum;
import com.accompany.core.exception.ServiceException;
import com.accompany.core.model.Users;
import com.accompany.core.mybatismapper.AccountLoginRecordMapperExpand;
import com.accompany.core.service.common.JedisService;
import com.accompany.core.service.user.UsersBaseService;
import com.accompany.core.util.JwtUtils;
import com.accompany.oauth2.support.h5.H5TokenGranter;
import com.accompany.oauth2.token.H5AccessToken;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.ServletWebRequest;
import java.util.Map;
/**
* Created by yuanyi on 2019/2/21.
*/
@Service
@Slf4j
public class AccountH5LoginService {
private static final long H5_JWT_TOKEN_EX = 60 * 60 * 1000 * 2L;
@Autowired
private UsersBaseService usersBaseService;
@Autowired
private AccountLoginRecordMapperExpand accountLoginRecordMapperExpand;
@Autowired
private JedisService jedisService;
@Autowired
private JwtUtils jwtUtils;
public String createJwtToken(Long uid) {
return jwtUtils.createJWT(H5_JWT_TOKEN_EX, uid);
}
private void saveH5LoginJwtToken(Long uid, String jwtToken) {
jedisService.hset(RedisKey.h5loginjwtoken.getKey(), uid.toString(), jwtToken);
}
private void deleteH5LoginJwtToken(Long uid) {
jedisService.hdel(RedisKey.h5loginjwtoken.getKey(), uid.toString());
}
/**
* 创建h5令牌
*
* @param uid
* @return
*/
public H5AccessToken createH5AccessToken(Long uid) {
//限制只是华语区
Users u = usersBaseService.getUsersByUid(uid);
if (null == u){
throw new ServiceException(BusiStatus.USERNOTEXISTS);
}
if (u.getPartitionId() != PartitionEnum.CHINESE.getId()) {
Integer userCount = usersBaseService.rechargeUserCount(uid);
if (userCount <= 0) {
throw new ServiceException(BusiStatus.H5_RECHARGE_USER_NOT_OPEN);
}
}
String jwtToken = createJwtToken(uid);
saveH5LoginJwtToken(uid, jwtToken);
H5AccessToken accessToken = new H5AccessToken();
accessToken.setPartitionId(u.getPartitionId());
accessToken.setAccess_token(jwtToken);
accessToken.setUid(uid);
accessToken.setExpires_in(H5_JWT_TOKEN_EX);
return accessToken;
}
/**
* 获取token
*
* @param request
* @return
*/
public H5AccessToken token(ServletWebRequest request) {
String grantType = request.getParameter("grant_type");
Map<String, H5TokenGranter> tokenGranterMap = SpringContextHolder.getApplicationContext().getBeansOfType(H5TokenGranter.class);
for (H5TokenGranter tokenGranter : tokenGranterMap.values()) {
if (tokenGranter.getGrantType().equals(grantType)) {
H5AccessToken token = tokenGranter.getAuthentication(request);
if (token != null) {
Long uid = token.getUid();
Integer isExists = accountLoginRecordMapperExpand.isExists(uid);
if (isExists > 0) {
deleteH5LoginJwtToken(uid);
throw new ServiceException(BusiStatus.REGION_NOT_OPEN_UP);
}
}
return token;
}
}
return null;
}
}

View File

@@ -1,47 +0,0 @@
package com.accompany.oauth2.support.h5;
import com.accompany.oauth2.token.H5AccessToken;
import org.springframework.web.context.request.ServletWebRequest;
import java.util.HashMap;
import java.util.Map;
/**
* @author: liaozetao
* @date: 2023/7/17 10:25
* @description:
*/
public abstract class AbstractH5TokenGranter implements H5TokenGranter {
protected static final String PHONE_AREA_CODE = "phoneAreaCode";
protected static final String PHONE = "phone";
protected static final String PASSWORD = "password";
protected static final String CODE = "code";
private final String grantType;
public AbstractH5TokenGranter(String grantType) {
this.grantType = grantType;
}
public H5AccessToken getAuthentication(ServletWebRequest request) {
Map<String, Object> parameters = new HashMap<>();
for (Map.Entry<String, String[]> entry : request.getParameterMap().entrySet()) {
String key = entry.getKey();
String[] value = entry.getValue();
if (value.length > 0) {
parameters.put(key, value[0]);
}
}
return authenticate(parameters);
}
public abstract H5AccessToken authenticate(Map<String, Object> parameters);
public String getGrantType() {
return grantType;
}
}

View File

@@ -1,27 +0,0 @@
package com.accompany.oauth2.support.h5;
import com.accompany.oauth2.token.H5AccessToken;
import org.springframework.web.context.request.ServletWebRequest;
/**
* @author: liaozetao
* @date: 2023/7/17 12:16
* @description:
*/
public interface H5TokenGranter {
/**
* 获取令牌
*
* @param request
* @return
*/
H5AccessToken getAuthentication(ServletWebRequest request);
/**
* 类型
*
* @return
*/
String getGrantType();
}

View File

@@ -1,71 +0,0 @@
package com.accompany.oauth2.support.h5;
import cn.hutool.core.util.StrUtil;
import com.accompany.common.device.DeviceInfo;
import com.accompany.common.utils.DESUtils;
import com.accompany.core.util.KeyStore;
import com.accompany.core.util.MD5;
import com.accompany.oauth2.constant.GrantTypeEnum;
import com.accompany.oauth2.constant.LoginTypeEnum;
import com.accompany.oauth2.model.AccountDetails;
import com.accompany.oauth2.service.MyUserDetailsService;
import com.accompany.oauth2.service.account.AccountH5LoginService;
import com.accompany.oauth2.token.H5AccessToken;
import com.accompany.oauth2.util.RequestContextHolderUtils;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.beanutils.BeanUtils;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Map;
/**
* @author: liaozetao
* @date: 2023/7/17 10:37
* @description:
*/
@Slf4j
public class PasswordH5TokenGranter extends AbstractH5TokenGranter {
private final MyUserDetailsService userDetailsService;
private final AccountH5LoginService accountH5LoginService;
public PasswordH5TokenGranter(MyUserDetailsService userDetailsService, AccountH5LoginService accountH5LoginService) {
super(GrantTypeEnum.PASSWORD.getValue());
this.userDetailsService = userDetailsService;
this.accountH5LoginService = accountH5LoginService;
}
@SneakyThrows
@Override
public H5AccessToken authenticate(Map<String, Object> parameters) {
String phoneAreaCode = StrUtil.toString(parameters.get(PHONE_AREA_CODE));
String username = StrUtil.toString(parameters.get(PHONE));
String password = StrUtil.toString(parameters.get(PASSWORD));
String code = StrUtil.toString(parameters.get(CODE));
String ipAddress = RequestContextHolderUtils.getRemoteAddr();
DeviceInfo deviceInfo = new DeviceInfo();
try {
BeanUtils.populate(deviceInfo, parameters);
} catch (Exception e) {
log.error(e.getMessage(), e);
}
UserDetails userDetails;
try {
username = DESUtils.DESAndBase64Decrypt(username, KeyStore.DES_ENCRYPT_KEY);
userDetails = userDetailsService.loadUserByPhone(username, phoneAreaCode, code, deviceInfo, ipAddress);
try {
password = MD5.getMD5(DESUtils.DESAndBase64Decrypt(password, KeyStore.DES_ENCRYPT_KEY));
} catch (Exception e) {
throw new IllegalArgumentException("密码非法");
}
userDetailsService.handlePwdLogin(username, password, userDetails);
userDetailsService.login(username, userDetails, LoginTypeEnum.PASSWORD, deviceInfo, code);
} catch (Exception e) {
log.error(e.getMessage(), e);
throw e;
}
return accountH5LoginService.createH5AccessToken(((AccountDetails) userDetails).getAccount().getUid());
}
}

View File

@@ -1,70 +0,0 @@
package com.accompany.oauth2.support.h5;
import cn.hutool.core.util.StrUtil;
import com.accompany.common.device.DeviceInfo;
import com.accompany.common.status.BusiStatus;
import com.accompany.core.service.user.PhoneBlackService;
import com.accompany.oauth2.constant.GrantTypeEnum;
import com.accompany.oauth2.constant.LoginTypeEnum;
import com.accompany.oauth2.exception.CustomOAuth2Exception;
import com.accompany.oauth2.model.AccountDetails;
import com.accompany.oauth2.service.MyUserDetailsService;
import com.accompany.oauth2.service.account.AccountH5LoginService;
import com.accompany.oauth2.token.H5AccessToken;
import com.accompany.oauth2.util.RequestContextHolderUtils;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.beanutils.BeanUtils;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Map;
/**
* @author: liaozetao
* @date: 2023/7/17 10:38
* @description:
*/
@Slf4j
public class VerifyCodeH5TokenGranter extends AbstractH5TokenGranter {
private final MyUserDetailsService userDetailsService;
private final PhoneBlackService phoneBlackService;
private final AccountH5LoginService accountH5LoginService;
public VerifyCodeH5TokenGranter(MyUserDetailsService userDetailsService, PhoneBlackService phoneBlackService, AccountH5LoginService accountH5LoginService) {
super(GrantTypeEnum.VERIFY_CODE.getValue());
this.userDetailsService = userDetailsService;
this.phoneBlackService = phoneBlackService;
this.accountH5LoginService = accountH5LoginService;
}
@SneakyThrows
@Override
public H5AccessToken authenticate(Map<String, Object> parameters) {
String phoneAreaCode = StrUtil.toString(parameters.get(PHONE_AREA_CODE));
String phone = StrUtil.toString(parameters.get(PHONE));
String code = StrUtil.toString(parameters.get(CODE));
DeviceInfo deviceInfo = new DeviceInfo();
try {
BeanUtils.populate(deviceInfo, parameters);
} catch (Exception e) {
log.error(e.getMessage(), e);
}
if (phoneBlackService.checkIsNeedIntercept(phone)) {
throw new CustomOAuth2Exception(CustomOAuth2Exception.PHONE_BE_INTERCEPTED, BusiStatus.PHONE_BE_INTERCEPTED.getReasonPhrase());
}
UserDetails userDetails = null;
try {
userDetails = userDetailsService.loadUserByPhone(phone, phoneAreaCode, code, deviceInfo, RequestContextHolderUtils.getRemoteAddr());
userDetailsService.login(phone, userDetails, LoginTypeEnum.ID, deviceInfo, code);
} catch (CustomOAuth2Exception e) {
throw e;
} catch (Exception e) {
log.error(e.getMessage(), e);
throw e;
}
return accountH5LoginService.createH5AccessToken(((AccountDetails) userDetails).getAccount().getUid());
}
}

View File

@@ -19,9 +19,6 @@ public class H5AccessToken {
@ApiModelProperty("用户ID")
private Long uid;
@ApiModelProperty("分区id")
private Integer partitionId;
/**
* 令牌
*/

View File

@@ -4,11 +4,7 @@ import com.accompany.core.service.SysConfService;
import com.accompany.core.service.user.PhoneBlackService;
import com.accompany.oauth2.service.MyUserDetailsService;
import com.accompany.oauth2.service.MyUserDetailsServiceImpl;
import com.accompany.oauth2.service.account.AccountH5LoginService;
import com.accompany.oauth2.support.email.EmailAuthenticationProvider;
import com.accompany.oauth2.support.h5.H5TokenGranter;
import com.accompany.oauth2.support.h5.PasswordH5TokenGranter;
import com.accompany.oauth2.support.h5.VerifyCodeH5TokenGranter;
import com.accompany.oauth2.support.password.PasswordAuthenticationProvider;
import com.accompany.oauth2.support.verify.VerifyCodeAuthenticationProvider;
import org.springframework.beans.factory.annotation.Autowired;
@@ -34,9 +30,6 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private PhoneBlackService phoneBlackService;
@Autowired
private AccountH5LoginService accountH5LoginService;
@Bean
@Override
protected UserDetailsService userDetailsService() {
@@ -90,13 +83,4 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
return new EmailAuthenticationProvider(myUserDetailsService());
}
@Bean
public H5TokenGranter passwordH5TokenGranter() {
return new PasswordH5TokenGranter(myUserDetailsService(), accountH5LoginService);
}
@Bean
public H5TokenGranter verifyCodeH5TokenGranter() {
return new VerifyCodeH5TokenGranter(myUserDetailsService(), phoneBlackService, accountH5LoginService);
}
}

View File

@@ -1,47 +0,0 @@
package com.accompany.oauth2.controller;
import com.accompany.common.result.BusiResult;
import com.accompany.core.exception.ServiceException;
import com.accompany.oauth2.common.BaseController;
import com.accompany.oauth2.service.account.AccountH5LoginService;
import com.accompany.oauth2.token.H5AccessToken;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.ServletWebRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Created by yuanyi on 2019/2/22.
*/
@Slf4j
@RestController
@RequestMapping("/oauth/h5")
public class H5LoginController extends BaseController {
@Autowired
private AccountH5LoginService accountH5LoginService;
/**
* 授权登录
*
* @param request
* @param response
* @return
*/
@ApiOperation("授权登录")
@PostMapping("/token")
public BusiResult<H5AccessToken> token(HttpServletRequest request, HttpServletResponse response) {
try {
return BusiResult.success(accountH5LoginService.token(new ServletWebRequest(request, response)));
} catch (ServiceException e) {
return BusiResult.fail(e.getBusiStatus(), e.getMessage());
}
}
}

View File

@@ -15,6 +15,7 @@
<module>accompany-base</module>
<module>accompany-business</module>
<module>accompany-oauth2</module>
<module>accompany-oauth</module>
<module>accompany-scheduler</module>
<module>accompany-mq</module>
</modules>