我的小程序接口被刷爆了

自然流量的驚喜

      書接上文,憑着短視頻的好奇,搭了個小程序,做了文案提取,配音等功能,也順帶寫了兩篇口水文章,不曾想居然收穫歷史最高的點贊與收藏。有興趣的朋友可以點這裏一看究盡:《短視頻配音原來如此簡單》,《短視頻文案提取的簡單實現》。做爲一個食人間煙火的程序員,也偷偷的去看了數據,由於沒抱太大的期望,自然流量給了我一個大大的驚喜,下圖是沒有任何推廣的數據。我一度暗暗自喜,直到我上線了另一個小程序做對照組時,也就是上兩個文章中提到的小程序,幾乎沒有自然流量,即使這個小程序功能更全,體驗更好,所有用戶都是從我的文章中關聯而來的,沒有自然流量。我瞬間明白了:論小程序取名的重要性。

 

 

告警呼嘯而至

        小程序上線後,總於可以睡上安穩覺了。於是又開始早上6:30去學校帶小朋友跑步了。跑了一年了,好幾個小朋友算是跑上道了。跑得正酣暢淋漓之時,突然,企微告警羣開始咚咚告警:resource pack exhausted! Please purchase resource packs... 30小時的資源包纔買幾天怎麼就耗盡了呢。跑完步,在學校噌了早飯,小電驢兒一溜煙回家打開電腦,巴拉出訪問日誌,傻眼了。這樣一個沒名沒份的小程序,居然有人在刷它的接口(大部分都是視頻文案提取,原來還有這麼多人在做短視頻),心中頓感五味雜成,有人刷說明功能還不多,這樣刷地主家也沒有餘糧了...

 

 

簽名保駕護航

        既然來了,只能接招了。既然刷接口,那就對接口訪問做一些校驗。目前小程序只是提文案提取等功能,所以首先想到接口做個簽名,防止別人使用程序自動刷。考慮小程序源碼獲取比較困難,簽名字段根據sha1簡單生成就可以了,未來如果這個也行不通,再使用RSA加密下sign字段就可以了。sign生成規則比較簡單,timestamp,request,隨機串,請求參數,排序 sha1就可以了。代碼如下。

前端只需要在request中 生成簽名,放到header裏就行了。

function sign(json) {
  json.timestamp = getTimestamp();
  json.rand = mtRand(100000, 999999);
  json.appkey = app.globalData.secretKey;

  let valueArray = [];
  for (let key in json) {
    valueArray.push(json[key]);
  }
  valueArray.sort();

  let signStr = jsonVAL(valueArray);
  console.log("signStr", signStr);

  json.sign = sha1Util.sha1(signStr);
  delete json.appkey;
  return json;
}

 

後端也簡單,根據一樣的規則,一樣的key,生成sign,對比前端的sign字段就可以了。自定義HandlerInterceptor,並註冊到InterceptorRegistry中就。

代碼如下;

/**
 * sign校驗攔截器
 * @author JJ
 */
@Slf4j
@Component
public class CheckSignInterceptor implements HandlerInterceptor {

    private static final String SecretKey = "*******";
    // 簽名過期時間(s)
    private static final Integer TimestampOut = 300;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {


        RequestWrapper requestWrapper = new RequestWrapper(request);
        String body = requestWrapper.getBody();
        Result result = this.check(body);
        if (!result.getSuccess()) {
            log.info("簽名失敗:{}", body);
            // 設置狀態碼爲401,表示未授權
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            // 設置響應內容類型和字符集
            response.setContentType("application/json;charset=UTF-8");
            // 自定義輸出
            response.getWriter().write(JSONUtil.toJsonStr(result));
            // 返回false阻止後續處理
            return false;
        }
        return true;
    }

    /**
     * token校驗
     * @param token
     * @return
     */
    private Result check(String body) {

        JSONObject jsonObject = JSONUtil.parseObj(body);
        String sign = "";
        Long timestamp = 0L;
        // jsonObject 值輸入有序列表。
        List<String> paramsValueList = new ArrayList<>();
        Set<Map.Entry<String, Object>> entries = jsonObject.entrySet();
        for (Map.Entry<String, Object> entry : entries) {
            String key = entry.getKey();
            Object value = entry.getValue();
            if (key.equals("sign")){
                sign = value.toString();
                continue;
            }
            if (key.equals("timestamp")){
                //如果時間戳爲空
                if (Strings.isNullOrEmpty(value.toString())){
                    return Result.failed(ErrorCodeEnum.ILLEGAL_ARGUMENT.code(), "時間戳不能爲空");
                }
                timestamp = Long.parseLong(value.toString());
            }
            paramsValueList.add(value.toString());
        }
        paramsValueList.add(SecretKey);
        Collections.sort(paramsValueList);

        //判斷時間是否大於5分鐘
        if (System.currentTimeMillis()/1000 - timestamp > TimestampOut){
            //return Result.failed(ErrorCodeEnum.ILLEGAL_ARGUMENT.code(), "時間戳無效");
        }
        String signStr = "";
        for (String value : paramsValueList) {
            signStr += value;
        }
        log.info("signStr:{}", signStr);
        String sha1Str = SecureUtil.sha1(signStr);
        if (sha1Str.equals(sign)){
            return Result.success();
        }
        return Result.failed(ErrorCodeEnum.ILLEGAL_ARGUMENT.code(), "簽名失敗");
    }

}

 

/**
 * @author JJ
 * @Classname InterceptorConfig
 */
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {

    @Resource
    CheckTokenInterceptor checkTokenInterceptor;
    @Resource
    CheckSignInterceptor checkSignInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(checkSignInterceptor).order(1);
       
    }
}

 

以上代碼基本都是copy的原有代碼,沒半天就上線了,自以爲可以高枕無憂了。

 

啥也擋不住RPA

       上線後,購買了資源包,也提心吊膽的統計着使用量。過了辦天,又有幾個用戶提取了超過70條視頻的文案。我一度懷疑簽名沒生效,直到我看非常規律的調用,我知道了,RPA來了。之前公司買過一個叫影刀RPA軟件,也玩了一些時間,編寫過一些自動化任務。它可以模擬人操作行爲,完成自動化任務,當然,我一直認爲未來RPA會有更多業務場景,一些邏輯明確的重複的事,都會由它們來完成。難怪小程序數據裏有不少是從pc打開的。我意識到我被薅羊毛了。

 

無奈只能限量了

       本着大家都有機會體驗這個小程序的原則,無奈之下,只能給每人每日限量了,畢竟小程序沒有收入。再本着能每個人都有極致體驗的機會,我限制了每人每天每個功能30次。這下基本上都限制到了,但是看着那些個RPA機器人,一大早就毫無感情的把30次機會耗盡,於是又增加了按UserId配置額度的功能,優先級高於按功能分配的額度。一頓操作後,總算是基本控制住了。又心累又心喜。喜在小程序給部分人帶來了價值,即便是用RPA的那些人也是有價值,雖然沒有感情。累的是又不得不處理這些煩瑣之事。

 

寫在最後

最近短劇火了起來,就有不少人開始提取長視頻的文案以及長視頻去水印。考慮到微信保存視頻時,有個200M的限制,又在考慮支持視頻文件壓縮功能了。跟本停不下來了,把寫代碼當成樂趣也是不錯的一件事兒。

 

有興趣的同學可以掃碼體驗下小程序(小程序名稱正在申請修改名稱,建議掃碼)

小程序名稱 :智能配音實用工具;

小程序二維碼 : 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章