Explorar el Código

Merge branch 'double_verify'

# Conflicts:
#	netflix-dao/src/main/java/com/cyksj/model/entity/Account.java
#	netflix-dao/src/main/java/com/cyksj/model/manage/views/AccountView.java
#	netflix-web/src/main/java/com/cyksj/web/controller/manage/account/CmsAccountController.java
zoujiajian hace 3 años
padre
commit
98ffaa6cca
Se han modificado 17 ficheros con 474 adiciones y 21 borrados
  1. 116 0
      netflix-common/src/main/java/com/cyksj/common/util/GoogleGenerator.java
  2. 17 0
      netflix-dao/src/main/java/com/cyksj/mapper/AccountDoubleVerifyRecordMapper.java
  3. 13 0
      netflix-dao/src/main/java/com/cyksj/mapper/DoubleVerifyClickRecordMapper.java
  4. 9 0
      netflix-dao/src/main/java/com/cyksj/mapper/GroupsMapper.java
  5. 2 0
      netflix-dao/src/main/java/com/cyksj/mapper/GroupsRelationMapper.java
  6. 5 0
      netflix-dao/src/main/java/com/cyksj/model/entity/Account.java
  7. 28 0
      netflix-dao/src/main/java/com/cyksj/model/entity/AccountDoubleVerifyRecord.java
  8. 24 0
      netflix-dao/src/main/java/com/cyksj/model/entity/DoubleVerifyClickRecord.java
  9. 4 1
      netflix-dao/src/main/java/com/cyksj/model/manage/views/AccountView.java
  10. 7 0
      netflix-dao/src/main/java/com/cyksj/model/manage/views/GroupsRelationView.java
  11. 62 0
      netflix-dao/src/main/java/com/cyksj/model/views/DoubleVerifyAccountView.java
  12. 13 0
      netflix-dao/src/main/resources/mapper/AccountDoubleVerifyRecordMapper.xml
  13. 6 0
      netflix-dao/src/main/resources/mapper/GroupRelationMapper.xml
  14. 8 4
      netflix-service/src/main/java/com/cyksj/service/mange/group/CmsGroupServiceImpl.java
  15. 23 10
      netflix-service/src/main/java/com/cyksj/task/Scheduler.java
  16. 79 3
      netflix-web/src/main/java/com/cyksj/web/controller/group/GroupRelationController.java
  17. 58 3
      netflix-web/src/main/java/com/cyksj/web/controller/manage/account/CmsAccountController.java

+ 116 - 0
netflix-common/src/main/java/com/cyksj/common/util/GoogleGenerator.java

@@ -0,0 +1,116 @@
+package com.cyksj.common.util;
+
+import org.apache.commons.codec.binary.Base32;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+
+/**
+ * @author chan
+ * @date 2023-05-18 5:17 PM
+ */
+public class GoogleGenerator {
+
+    // 最多可偏移的时间
+    static int window_size = 1; // default 3 -˚ max 17
+
+    /**
+     * set the windows size. This is an integer value representing the number of
+     * 30 second windows we allow The bigger the window, the more tolerant of
+     * clock skew we are.
+     *
+     * @param s window size - must be >=1 and <=17. Other values are ignored
+     */
+    public void setWindowSize(int s) {
+        if (s >= 1 && s <= 17)
+            window_size = s;
+    }
+
+
+    /**
+     * Check the code entered by the user to see if it is valid 验证code是否合法
+     *
+     * @param secret The users secret.
+     * @param code   The code displayed on the users device
+     * @param t      The time in msec (System.currentTimeMillis() for example)
+     * @return
+     */
+    public boolean check_code(String secret, long code, long timeMsec) {
+        Base32 codec = new Base32();
+        byte[] decodedKey = codec.decode(secret);
+        // convert unix msec time into a 30 second "window"
+        // this is per the TOTP spec (see the RFC for details)
+        long t = (timeMsec / 1000L) / 30L;
+        // Window is used to check codes generated in the near past.
+        // You can use this value to tune how far you're willing to go.
+        for (int i = -window_size; i <= window_size; ++i) {
+            long hash;
+            try {
+                hash = verify_code(decodedKey, t + i);
+            } catch (Exception e) {
+                // Yes, this is bad form - but
+                // the exceptions thrown would be rare and a static
+                // configuration problem
+                e.printStackTrace();
+                throw new RuntimeException(e.getMessage());
+                // return false;
+            }
+            if (hash == code) {
+                return true;
+            }
+        }
+        // The validation code is invalid.
+        return false;
+    }
+    public static long getCode(String secret, long timeMsec) {
+        Base32 codec = new Base32();
+        byte[] decodedKey = codec.decode(secret);
+        // convert unix msec time into a 30 second "window"
+        // this is per the TOTP spec (see the RFC for details)
+        long t = (timeMsec / 1000L) / 30L;
+        // Window is used to check codes generated in the near past.
+        // You can use this value to tune how far you're willing to go.
+        for (int i = -window_size; i <= window_size; ++i) {
+            long hash;
+            try {
+                hash = verify_code(decodedKey, t + i);
+            } catch (Exception e) {
+                // Yes, this is bad form - but
+                // the exceptions thrown would be rare and a static
+                // configuration problem
+                e.printStackTrace();
+                throw new RuntimeException(e.getMessage());
+                // return false;
+            }
+                return hash;
+        }
+        // The validation code is invalid.
+        return 0;
+    }
+
+    private static int verify_code(byte[] key, long t) throws NoSuchAlgorithmException, InvalidKeyException {
+        byte[] data = new byte[8];
+        long value = t;
+        for (int i = 8; i-- > 0; value >>>= 8) {
+            data[i] = (byte) value;
+        }
+        SecretKeySpec signKey = new SecretKeySpec(key, "HmacSHA1");
+        Mac mac = Mac.getInstance("HmacSHA1");
+        mac.init(signKey);
+        byte[] hash = mac.doFinal(data);
+        int offset = hash[20 - 1] & 0xF;
+        // We're using a long because Java hasn't got unsigned int.
+        long truncatedHash = 0;
+        for (int i = 0; i < 4; ++i) {
+            truncatedHash <<= 8;
+            // We are dealing with signed bytes:
+            // we just keep the first byte.
+            truncatedHash |= (hash[offset + i] & 0xFF);
+        }
+        truncatedHash &= 0x7FFFFFFF;
+        truncatedHash %= 1000000;
+        return (int) truncatedHash;
+    }
+}

+ 17 - 0
netflix-dao/src/main/java/com/cyksj/mapper/AccountDoubleVerifyRecordMapper.java

@@ -0,0 +1,17 @@
+package com.cyksj.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.cyksj.model.entity.AccountDoubleVerifyRecord;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/*
+ *项目名: netflix
+ *文件名: AccountDoubleVerifyRecordMapper
+ *创建者: JavaZou
+ *创建时间:2023/5/19 11:32
+ */
+public interface AccountDoubleVerifyRecordMapper extends BaseMapper<AccountDoubleVerifyRecord> {
+	void batchInsert(@Param("list") List<AccountDoubleVerifyRecord> list);
+}

+ 13 - 0
netflix-dao/src/main/java/com/cyksj/mapper/DoubleVerifyClickRecordMapper.java

@@ -0,0 +1,13 @@
+package com.cyksj.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.cyksj.model.entity.DoubleVerifyClickRecord;
+
+/*
+ *项目名: netflix
+ *文件名: DoubleVerifyClickRecordMapper
+ *创建者: JavaZou
+ *创建时间:2023/5/19 18:42
+ */
+public interface DoubleVerifyClickRecordMapper extends BaseMapper<DoubleVerifyClickRecord> {
+}

+ 9 - 0
netflix-dao/src/main/java/com/cyksj/mapper/GroupsMapper.java

@@ -1,10 +1,13 @@
 package com.cyksj.mapper;
 
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.cyksj.model.entity.AccountDoubleVerifyRecord;
 import com.cyksj.model.entity.GroupsTrips;
 import org.apache.ibatis.annotations.Select;
 import org.apache.ibatis.annotations.Update;
 
+import java.util.List;
+
 /**
  * @author chan
  * @date 2022/9/27 4:24 PM
@@ -18,4 +21,10 @@ public interface GroupsMapper extends BaseMapper<GroupsTrips> {
 
     @Select("select count(*) from groups_trips where sku_id = #{skuId} and num = available_num")
 	Integer selectEmptyNetfilxTrips(Long skuId);
+
+	@Select("select gt.* from (select a.account,t.account_id,t.id as groups_id,t.sku_id,t.num from account a inner join groups_trips t on a.id = t.account_id where t.sku_id in (select id from goods_don_sku where goods_id = #{goodsId} and status is true) and t.num > 1 and a.api_secret = '') gt" +
+			" where gt.num = (select count(*) from groups_relation gr where gr.groups_id = gt.groups_id and gr.`status` = 'validity')" +
+			" and not EXISTS (select account_id from account_double_verify_record where account_id = gt.account_id and deleted is true)" +
+			" and DATE_SUB(now(),INTERVAL 3 day) >= (select max(update_time) from groups_relation gr where gr.groups_id = gt.groups_id and gr.`status` = 'validity')")
+	List<AccountDoubleVerifyRecord> selectGPTDoubleVerifyTrips(Long goodsId);
 }

+ 2 - 0
netflix-dao/src/main/java/com/cyksj/mapper/GroupsRelationMapper.java

@@ -32,4 +32,6 @@ public interface GroupsRelationMapper extends BaseMapper<GroupsRelation> {
 	GroupsRelationView getGroupRelationViewByUserIdAndRelationId(@Param("userId") Long userId, @Param("relationId") Long relationId);
 
 	String getBindPhoneByUserId(Long userId);
+
+	Integer selectViewPwdCount(Long groupsId);
 }

+ 5 - 0
netflix-dao/src/main/java/com/cyksj/model/entity/Account.java

@@ -65,4 +65,9 @@ public class Account extends BaseEntity {
      * 密保
      */
     private String secret;
+
+    /**
+     * gpt API 密钥
+     */
+    private String apiSecret;
 }

+ 28 - 0
netflix-dao/src/main/java/com/cyksj/model/entity/AccountDoubleVerifyRecord.java

@@ -0,0 +1,28 @@
+package com.cyksj.model.entity;
+
+import lombok.Getter;
+import lombok.Setter;
+
+/*
+ *项目名: netflix
+ *文件名: AccountDoubleVerifyRecord
+ *创建者: JavaZou
+ *创建时间:2023/5/19 11:29
+ */
+@Getter
+@Setter
+public class AccountDoubleVerifyRecord extends BaseEntity{
+	private Long groupsId;
+
+	private Long accountId;
+
+	private String account;
+
+	private Long skuId;
+
+	private String remark;
+
+	private String apiSecret;
+
+	private Boolean deleted;
+}

+ 24 - 0
netflix-dao/src/main/java/com/cyksj/model/entity/DoubleVerifyClickRecord.java

@@ -0,0 +1,24 @@
+package com.cyksj.model.entity;
+
+import lombok.Getter;
+import lombok.Setter;
+
+/*
+ *项目名: netflix
+ *文件名: DoubleVerifyClickRecord
+ *创建者: JavaZou
+ *创建时间:2023/5/19 18:40
+ */
+@Getter
+@Setter
+public class DoubleVerifyClickRecord extends BaseEntity{
+	private Long userId;
+
+	private Long accountId;
+
+	private String account;
+
+	private Long skuId;
+
+	private Long relationId;
+}

+ 4 - 1
netflix-dao/src/main/java/com/cyksj/model/manage/views/AccountView.java

@@ -19,7 +19,7 @@ import java.util.Date;
         "left join groups_trips gt on gt.account_id = a.id " +
         "left join groups_relation gr on gr.groups_id = gt.id " +
         "left join goods_don_sku gdk on gdk.id = gt.sku_id",
-        groupBy = "a.id,g.id,a.bank_card,g.title,a.account,a.password,a.api_key,a.verify_codes,a.start_time,a.expiry_time,a.amount,a.cn_account,a.us_account,gt.id,a.remark,gdk.id,gdk.spec_val,a.customer_service_id,a.secret")
+        groupBy = "a.id,g.id,a.bank_card,g.title,a.account,a.password,a.api_key,a.verify_codes,a.start_time,a.expiry_time,a.amount,a.cn_account,a.us_account,gt.id,a.remark,gdk.id,gdk.spec_val,a.customer_service_id,a.secret,a.api_secret")
 public class AccountView {
 
     @DbField("a.id")
@@ -46,6 +46,9 @@ public class AccountView {
     @DbField("a.verify_codes")
     private String verifyCodes;
 
+    @DbField("a.api_secret")
+    private String apiSecret;
+
     @DbField("a.start_time")
     private Date startTime;
 

+ 7 - 0
netflix-dao/src/main/java/com/cyksj/model/manage/views/GroupsRelationView.java

@@ -39,6 +39,13 @@ public class GroupsRelationView {
     @JsonIgnore
     private String tripsPassword;
 
+    @DbField("a.api_secret")
+    @JsonIgnore
+    private String apiSecret;
+
+    @DbField("if(a.api_secret='',false,true)")
+    private Boolean isVerify;
+
     @DbField("g.id")
     private Long goodsId;
 

+ 62 - 0
netflix-dao/src/main/java/com/cyksj/model/views/DoubleVerifyAccountView.java

@@ -0,0 +1,62 @@
+package com.cyksj.model.views;
+
+import com.ejlchina.searcher.bean.DbField;
+import com.ejlchina.searcher.bean.SearchBean;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.util.Date;
+
+/*
+ *项目名: netflix
+ *文件名: DoubleVerifyAccountView
+ *创建者: JavaZou
+ *创建时间:2023/5/19 14:28
+ */
+@Getter
+@Setter
+@SearchBean(tables = "account a inner join account_double_verify_record ar on a.id = ar.account_id and ar.deleted is true" +
+		" left join goods_don g on g.id = a.goods_id" +
+		" left join groups_trips gt on gt.id = ar.groups_id" +
+		" left join goods_don_sku sku on sku.id = gt.sku_id")
+public class DoubleVerifyAccountView {
+
+	@DbField("ar.id")
+	private Long recordId;
+
+	@DbField("a.id")
+	private Long id;
+
+	@DbField("g.title")
+	private String title;
+
+	@DbField("g.id")
+	private Long goodsId;
+
+	@DbField("sku.id")
+	private Long skuId;
+
+	@DbField("sku.spec_val")
+	private String specVal;
+
+	@DbField("a.account")
+	private String account;
+
+	@DbField("a.password")
+	private String password;
+
+	@DbField("a.start_time")
+	private Date startTime;
+
+	@DbField("a.expiry_time")
+	private Date expiryTime;
+
+	@DbField("gt.num")
+	private Integer num;
+
+	@DbField("a.api_secret")
+	private String apiSecret;
+
+	@DbField("ar.remark")
+	private String reason;
+}

+ 13 - 0
netflix-dao/src/main/resources/mapper/AccountDoubleVerifyRecordMapper.xml

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.cyksj.mapper.AccountDoubleVerifyRecordMapper">
+
+    <insert id="batchInsert">
+        insert into
+        account_double_verify_record(`account_id`,`account`,`groups_id`,`sku_id`,`remark`,`created_time`,`update_time`)
+        values
+        <foreach collection="list" item="item" separator=",">
+            (#{item.accountId},#{item.account},#{item.groupsId},#{item.skuId},'车队满员后三天后自动移入',now(),now())
+        </foreach>
+    </insert>
+</mapper>

+ 6 - 0
netflix-dao/src/main/resources/mapper/GroupRelationMapper.xml

@@ -85,4 +85,10 @@
         select if(login_phone = '',ud.phone,login_phone) as phone from (select id,login_phone from user where id = #{userId} limit 1) u left join user_bind_detail ud
         on ud.user_id = u.id
     </select>
+
+    <select id="selectViewPwdCount" resultType="java.lang.Integer">
+        select count(distinct relation_id)
+        from (select id, user_id from `groups_relation` where groups_id = #{groupsId} and `status` = 'validity') s
+                 inner join chat_gpt_pwd_view_record cr on cr.relation_id = s.id and s.user_id = cr.user_id
+    </select>
 </mapper>

+ 8 - 4
netflix-service/src/main/java/com/cyksj/service/mange/group/CmsGroupServiceImpl.java

@@ -25,9 +25,11 @@ import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
+import java.util.Arrays;
 import java.util.Date;
 import java.util.List;
 import java.util.Optional;
+import java.util.stream.Collectors;
 
 /**
  * @author chan
@@ -67,9 +69,11 @@ public class CmsGroupServiceImpl extends ServiceImpl<GroupsMapper,GroupsTrips> i
         if (sku == null) {
             throw new BusinessRuntimeException("该商品规格不存在!");
         }
-        String[] verifyCodeArray = null;
+        List<String> verifyCodeList = null;
         if (StrUtil.isNotBlank(verifyCodes)) {
-            verifyCodeArray = verifyCodes.split(",");
+            verifyCodes = verifyCodes.replaceAll("\n", "");
+            String[] verifyCodeArray = verifyCodes.split("\\*");
+            verifyCodeList = Arrays.stream(verifyCodeArray).filter(str -> StrUtil.isNotBlank(str)).map(str -> str.trim()).collect(Collectors.toList());
         }
         groups.setNum(sku.getNum());
         groups.setAvailableNum(sku.getNum());
@@ -84,8 +88,8 @@ public class CmsGroupServiceImpl extends ServiceImpl<GroupsMapper,GroupsTrips> i
             relation.setGroupsId(groups.getId());
             relation.setStatus(GroupsRelation.Status.none);
             relation.setNum(i);
-            if (verifyCodeArray != null && verifyCodeArray.length > 0 && verifyCodeArray.length >= i) {
-                relation.setVerifyCode(verifyCodeArray[i - 1]);
+            if (verifyCodeList != null && verifyCodeList.size() > 0 && verifyCodeList.size() >= i) {
+                relation.setVerifyCode(verifyCodeList.get(i - 1));
             }
             relationMapper.insert(relation);
         }

+ 23 - 10
netflix-service/src/main/java/com/cyksj/task/Scheduler.java

@@ -28,8 +28,6 @@ import com.cyksj.service.register.RegisterOrderDonService;
 import com.cyksj.service.register.SpotifyService;
 import com.cyksj.service.relation.GroupRelationClearService;
 import com.cyksj.service.sms.SmsService;
-import com.cyksj.service.template.TemplateCommonService;
-import com.cyksj.service.wechat.WeChatService;
 import com.ejlchina.searcher.BeanSearcher;
 import com.ejlchina.searcher.param.Operator;
 import com.ejlchina.searcher.util.MapUtils;
@@ -62,12 +60,6 @@ public class Scheduler {
 
         private final UserMapper userMapper;
 
-        private final GoodsDonSkuMapper skuMapper;
-
-        private final WeChatService weChatService;
-
-        private final AccountMapper accountMapper;
-
         private final BeanSearcher beanSearcher;
 
         private final SpotifyService spotifyService;
@@ -86,8 +78,6 @@ public class Scheduler {
 
         private final RealGoodsInterestUserMapper realGoodsInterestUserMapper;
 
-        private final TemplateCommonService templateCommonService;
-
         private final GroupRelationClearService groupRelationClearService;
 
         private final CmsAccountService cmsAccountService;
@@ -100,6 +90,10 @@ public class Scheduler {
 
         private final CmsUserMapper cmsUserMapper;
 
+	    private final AccountDoubleVerifyRecordMapper doubleVerifyRecordMapper;
+
+	    private final AccountDoubleVerifyRecordMapper accountDoubleVerifyRecordMapper;
+
 
     /**
      * 每5分钟清理过期账号
@@ -438,4 +432,23 @@ public class Scheduler {
 		    }
 	    });
     }
+
+	@Scheduled(cron = "0 0 0/3 * * ?")
+	public void accountDoubleVerify() {
+    	//chatGPT PLUS
+		Long goodsId = 18l;
+		List<AccountDoubleVerifyRecord> doubleVerifyRecords = groupsMapper.selectGPTDoubleVerifyTrips(goodsId);
+		doubleVerifyRecords.forEach(data -> {
+			AccountDoubleVerifyRecord exist = accountDoubleVerifyRecordMapper.selectOne(Wrappers.lambdaQuery(AccountDoubleVerifyRecord.class)
+					.eq(AccountDoubleVerifyRecord::getAccountId, data.getAccountId())
+					.eq(AccountDoubleVerifyRecord::getDeleted, true).last("limit 1"));
+			if (exist == null) {
+				if (redisService.setNx(String.format("%s-%s", data.getAccountId(), data.getId()), data.getId(), 60l)) {
+					log.info("插入待开启双重验证号账号:{}", data.getAccount());
+					data.setRemark("车队满员后三天后自动移入");
+					doubleVerifyRecordMapper.insert(data);
+				}
+			}
+		});
+	}
 }

+ 79 - 3
netflix-web/src/main/java/com/cyksj/web/controller/group/GroupRelationController.java

@@ -1,20 +1,26 @@
 package com.cyksj.web.controller.group;
 
+import cn.hutool.core.date.DateTime;
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.util.StrUtil;
 import cn.hutool.extra.servlet.ServletUtil;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.cyksj.common.exception.BusinessRuntimeException;
+import com.cyksj.common.task.GlobalThreadPoolTaskExecutor;
+import com.cyksj.common.util.GoogleGenerator;
 import com.cyksj.common.util.StringUtil;
 import com.cyksj.dto.Result;
 import com.cyksj.enums.GatewayResponse;
+import com.cyksj.mapper.AccountDoubleVerifyRecordMapper;
 import com.cyksj.mapper.ChatGptPwdViewRecordMapper;
+import com.cyksj.mapper.DoubleVerifyClickRecordMapper;
 import com.cyksj.mapper.GroupsRelationMapper;
-import com.cyksj.model.entity.ChatGptPwdViewRecord;
-import com.cyksj.model.entity.GroupsRelation;
-import com.cyksj.model.entity.OrderDon;
+import com.cyksj.model.entity.*;
 import com.cyksj.model.manage.views.GroupsRelationView;
 import com.cyksj.model.request.OrderPayRequest;
 import com.cyksj.model.views.RenewalView;
 import com.cyksj.model.views.UserTicketView;
+import com.cyksj.redis.RedisService;
 import com.cyksj.service.order.OrderDonService;
 import com.cyksj.service.relation.GroupRelationFrontService;
 import com.cyksj.service.user.UserService;
@@ -53,6 +59,14 @@ public class GroupRelationController {
 
     private final HttpServletRequest request;
 
+    private final AccountDoubleVerifyRecordMapper accountDoubleVerifyRecordMapper;
+
+    private final DoubleVerifyClickRecordMapper doubleVerifyRecordMapper;
+
+    private final RedisService redisService;
+
+    private final GlobalThreadPoolTaskExecutor THREAD_POOL;
+
     @RequestMapping("/get/renewal")
     public Result<List<RenewalView>> get(Boolean isLoginPopularize) {
         long userId = StpUserUtil.getLoginIdAsLong();
@@ -114,6 +128,7 @@ public class GroupRelationController {
         GroupsRelationView groupsRelationView = beanSearcher.searchFirst(GroupsRelationView.class, MapUtils.builder()
                 .field(GroupsRelationView::getUserId, userIds).op(Operator.InList)
                 .field(GroupsRelationView::getId, relationId).build());
+        if (groupsRelationView.getAccountId() == null) return GatewayResponse.SUCCESS.newBuilder().toResult();
         if (groupRelationFrontService == null) throw BusinessRuntimeException.getInstance("车票已不存在");
         ChatGptPwdViewRecord viewRecord = new ChatGptPwdViewRecord();
         viewRecord.setUserId(userId);
@@ -121,6 +136,67 @@ public class GroupRelationController {
         viewRecord.setSkuId(groupsRelationView.getSkuId());
         viewRecord.setOperateLocation(StringUtil.getRealAddressByIP(ServletUtil.getClientIP(request)));
         chatGptPwdViewRecordMapper.insert(viewRecord);
+        if (StrUtil.isEmpty(groupsRelationView.getApiSecret())) {
+            THREAD_POOL.execute(() -> {
+                //车队所有人查看密码
+                Integer num = groupsRelationView.getNum();
+                Integer viewNum = relationMapper.selectViewPwdCount(groupsRelationView.getGroupsId());
+                if (num <= viewNum) {
+                    AccountDoubleVerifyRecord exist = accountDoubleVerifyRecordMapper.selectOne(Wrappers.lambdaQuery(AccountDoubleVerifyRecord.class)
+                            .eq(AccountDoubleVerifyRecord::getAccountId, groupsRelationView.getAccountId())
+                            .eq(AccountDoubleVerifyRecord::getDeleted, true).last("limit 1"));
+                    if (exist == null) {
+                        if (redisService.setNx(String.format("%s-%s", groupsRelationView.getAccountId(), groupsRelationView.getId()), groupsRelationView.getId(), 60l)) {
+                            //记录
+                            AccountDoubleVerifyRecord accountDoubleVerifyRecord = new AccountDoubleVerifyRecord();
+                            accountDoubleVerifyRecord.setAccountId(groupsRelationView.getAccountId());
+                            accountDoubleVerifyRecord.setAccount(groupsRelationView.getTripsAccount());
+                            accountDoubleVerifyRecord.setGroupsId(groupsRelationView.getGroupsId());
+                            accountDoubleVerifyRecord.setSkuId(groupsRelationView.getSkuId());
+                            accountDoubleVerifyRecord.setRemark("车队所有人看过密码");
+                            accountDoubleVerifyRecordMapper.insert(accountDoubleVerifyRecord);
+                        }
+                    }
+                }
+            });
+        }
         return GatewayResponse.SUCCESS.newBuilder().toResult(groupsRelationView.getTripsPassword());
     }
+
+    /**
+     * 获取AI类 ChatGPT产品谷歌验证码
+     */
+    @GetMapping("/get/authCode")
+    public Result<String> getAuthCode(Long relationId) throws InterruptedException {
+        long userId = StpUserUtil.getLoginIdAsLong();
+        //每个人 每月只能获取两次
+        if (relationId==null) throw BusinessRuntimeException.getInstance("车票不存在");
+        GroupsRelationView groupsRelationView = beanSearcher.searchFirst(GroupsRelationView.class, MapUtils.builder()
+                .field(GroupsRelationView::getId, relationId)
+                .field(GroupsRelationView::getUserId, userId).build());
+        if (groupsRelationView == null) throw BusinessRuntimeException.getInstance("车票不存在");
+        if (StrUtil.isEmpty(groupsRelationView.getApiSecret())) throw BusinessRuntimeException.getInstance("未开启验证");
+        DateTime now = DateTime.now();
+        Integer count = doubleVerifyRecordMapper.selectCount(Wrappers.lambdaQuery(DoubleVerifyClickRecord.class)
+                .eq(DoubleVerifyClickRecord::getUserId, userId)
+                .eq(DoubleVerifyClickRecord::getAccountId, groupsRelationView.getAccountId())
+                .between(DoubleVerifyClickRecord::getCreatedTime, DateUtil.beginOfMonth(now), DateUtil.endOfMonth(now)));
+        if (count >= 2) throw BusinessRuntimeException.getInstance("获取次数上限");
+        int second = now.second();
+        if (second < 30 && 30 - second <= 10) {
+            Thread.sleep(30 - second + 1);
+        } else if (second > 30 && 60 - second <= 10) {
+            Thread.sleep(60 - second + 1);
+        }
+        DoubleVerifyClickRecord doubleVerifyClickRecord = new DoubleVerifyClickRecord();
+        doubleVerifyClickRecord.setRelationId(relationId);
+        doubleVerifyClickRecord.setUserId(userId);
+
+        doubleVerifyClickRecord.setAccountId(groupsRelationView.getAccountId());
+        doubleVerifyClickRecord.setAccount(groupsRelationView.getTripsAccount());
+        doubleVerifyClickRecord.setSkuId(groupsRelationView.getSkuId());
+        doubleVerifyRecordMapper.insert(doubleVerifyClickRecord);
+        long code = GoogleGenerator.getCode(groupsRelationView.getApiSecret(), now.getTime());
+        return GatewayResponse.SUCCESS.newBuilder().toResult(Long.toString(code));
+    }
 }

+ 58 - 3
netflix-web/src/main/java/com/cyksj/web/controller/manage/account/CmsAccountController.java

@@ -29,6 +29,7 @@ import com.cyksj.model.manage.views.GroupsRelationView;
 import com.cyksj.model.response.ExpiredAccountRep;
 import com.cyksj.model.response.OrderDonAccountRep;
 import com.cyksj.model.views.CustomerServiceUpdatePwdDetail;
+import com.cyksj.model.views.DoubleVerifyAccountView;
 import com.cyksj.model.views.GoodsDonSkuView;
 import com.cyksj.model.views.UserTicketClearedRecordView;
 import com.cyksj.service.mange.CmsAccountService;
@@ -52,6 +53,7 @@ import org.springframework.web.multipart.MultipartFile;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
+import java.util.Arrays;
 import java.util.List;
 import java.util.Optional;
 import java.util.Set;
@@ -92,6 +94,8 @@ public class CmsAccountController {
 
 	private final GroupsRelationMapper groupsRelationMapper;
 
+	private final AccountDoubleVerifyRecordMapper doubleVerifyRecordMapper;
+
     @GetMapping("/get")
     @SaCheckPermission("account:view")
     public Result<SearchResult<AccountView>> get(Long userId ,String nickName ,String submitAccount ,String expiryStartTime, String expiryEndTime) {
@@ -363,22 +367,30 @@ public class CmsAccountController {
 	    }
 	    String verifyCodes = account.getVerifyCodes();
 	    if (StrUtil.isNotBlank(verifyCodes) && !StrUtil.equals(verifyCodes, db.getVerifyCodes())) {
-		    String[] verifyCodesArray = verifyCodes.split(",");
+		    String[] verifyCodesArray = verifyCodes.split("\\*");
+		    List<String> verifyCodeList = Arrays.stream(verifyCodesArray).filter(str -> StrUtil.isNotBlank(str)).map(str -> str.trim()).collect(Collectors.toList());
 		    List<GroupsRelationView> relationViews = beanSearcher.searchAll(GroupsRelationView.class, MapUtils.builder()
 				    .field(GroupsRelationView::getAccountId, account.getId())
 				    .onlySelect(GroupsRelationView::getId)
 				    .build());
 		    if (CollUtil.isNotEmpty(relationViews)) {
-			    for (int i = 0; i < verifyCodesArray.length; i++) {
+			    for (int i = 0; i < verifyCodeList.size(); i++) {
 				    if (i < relationViews.size()) {
 					    GroupsRelationView groupsRelationView = relationViews.get(i);
 					    groupsRelationMapper.update(null, Wrappers.lambdaUpdate(GroupsRelation.class)
-							    .set(GroupsRelation::getVerifyCode, verifyCodesArray[i])
+							    .set(GroupsRelation::getVerifyCode, verifyCodeList.get(i))
 							    .eq(GroupsRelation::getId, groupsRelationView.getId()));
 				    }
 			    }
 		    }
 	    }
+	    String apiSecret = account.getApiSecret();
+	    if (StrUtil.isNotBlank(apiSecret)) {
+		    doubleVerifyRecordMapper.update(null, Wrappers.lambdaUpdate(AccountDoubleVerifyRecord.class)
+				    .set(AccountDoubleVerifyRecord::getDeleted, false)
+				    .set(AccountDoubleVerifyRecord::getApiSecret, apiSecret)
+				    .eq(AccountDoubleVerifyRecord::getAccountId, account.getId()));
+	    }
 	    account.setStartTime(null);
 	    accountService.updateById(account);
         return GatewayResponse.SUCCESS.newBuilder().toResult();
@@ -608,4 +620,47 @@ public class CmsAccountController {
 		accountService.analysisAmericaAppidAccount(goodsId, skuId, accountList);
 		return GatewayResponse.SUCCESS.newBuilder().toResult();
 	}
+
+	/**
+	 * 等待双重认证gpt账号列表
+	 */
+	@GetMapping("/get/doubleVerifyAccount")
+	public Result<SearchResult<DoubleVerifyAccountView>> getDoubleVerifyAccount() {
+		SearchResult<DoubleVerifyAccountView> search = beanSearcher.search(DoubleVerifyAccountView.class, MapUtils.flatBuilder(request.getParameterMap()).build());
+		return GatewayResponse.SUCCESS.newBuilder().toResult(search);
+	}
+
+	/**
+	 * 编辑双重认证账号
+	 */
+	@PutMapping("/put/doubleVerifyAccount")
+	public Result<String> updateDoubleVerifyAccount(@RequestBody Account account) throws Exception {
+		Long id = account.getId();
+		Account db = accountService.getById(id);
+		String dnPwd = db.getPassword();
+		String newPwd = account.getPassword();
+
+		String apiSecret = account.getApiSecret();
+		boolean isFlag = false;
+		if (StrUtil.isNotBlank(apiSecret)) {
+			db.setApiSecret(apiSecret);
+			isFlag = true;
+			doubleVerifyRecordMapper.update(null, Wrappers.lambdaUpdate(AccountDoubleVerifyRecord.class)
+					.set(AccountDoubleVerifyRecord::getDeleted, false)
+					.set(AccountDoubleVerifyRecord::getApiSecret, apiSecret)
+					.eq(AccountDoubleVerifyRecord::getAccountId, id));
+		}
+		if (StrUtil.isNotBlank(newPwd)) {
+			newPwd = newPwd.trim();
+			db.setPassword(newPwd);
+			isFlag = true;
+		}
+		if (isFlag) {
+			boolean b = accountService.updateById(db);
+			if (StrUtil.isNotBlank(newPwd) && !dnPwd.equals(newPwd) && b) {
+				templateCommonService.sendAccountTemplateMsg(account);
+			}
+		}
+		return GatewayResponse.SUCCESS.newBuilder().toResult();
+	}
 }