我的小程式介面被刷爆了

2J發表於2024-04-13

自然流量的驚喜

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

告警呼嘯而至

小程式上線後,總於可以睡上安穩覺了。於是又開始早上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的限制,又在考慮支援影片檔案壓縮功能了。跟本停不下來了,把寫程式碼當成樂趣也是不錯的一件事兒。

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

小程式名稱 :智慧配音實用工具;

小程式二維碼 :

相關文章