Subversion Repositories SmartDukaan

Rev

Rev 37407 | Rev 37446 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
29451 manish 1
package com.smartdukaan.cron.scheduled;
2
 
36420 amit 3
import com.spice.profitmandi.common.web.client.HttpClientFactory;
30209 amit.gupta 4
import com.spice.profitmandi.dao.model.ImeiActivationTimestampModel;
5
import com.spice.profitmandi.dao.repository.fofo.ActivatedImeiRepository;
6
import com.spice.profitmandi.service.inventory.InventoryService;
36420 amit 7
import org.apache.http.NameValuePair;
8
import org.apache.http.client.entity.UrlEncodedFormEntity;
9
import org.apache.http.client.methods.CloseableHttpResponse;
10
import org.apache.http.client.methods.HttpGet;
11
import org.apache.http.client.methods.HttpPost;
12
import org.apache.http.impl.client.BasicCookieStore;
13
import org.apache.http.impl.client.CloseableHttpClient;
14
import org.apache.http.message.BasicNameValuePair;
15
import org.apache.http.util.EntityUtils;
29451 manish 16
import org.apache.logging.log4j.LogManager;
17
import org.apache.logging.log4j.Logger;
18
import org.json.JSONObject;
19
import org.springframework.beans.factory.annotation.Autowired;
20
import org.springframework.stereotype.Component;
36260 amit 21
import org.springframework.transaction.annotation.Propagation;
22
import org.springframework.transaction.annotation.Transactional;
29451 manish 23
 
36420 amit 24
import java.io.IOException;
25
import java.nio.charset.StandardCharsets;
30209 amit.gupta 26
import java.time.LocalDate;
27
import java.time.format.DateTimeFormatter;
30335 amit.gupta 28
import java.util.ArrayList;
29
import java.util.List;
29451 manish 30
 
31
@Component
32
class VivoImeiActivationService {
33
 
33453 amit.gupta 34
    @Autowired
35
    InventoryService inventoryService;
29451 manish 36
 
33453 amit.gupta 37
    @Autowired
38
    CaptchaService captchaService;
29466 amit.gupta 39
 
33453 amit.gupta 40
    @Autowired
41
    ActivatedImeiRepository activatedImeiRepository;
29451 manish 42
 
33453 amit.gupta 43
    private static final Logger LOGGER = LogManager.getLogger(VivoImeiActivationService.class);
29451 manish 44
 
36260 amit 45
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
33453 amit.gupta 46
    public void checkImeiActivation() throws Exception {
47
        if (LocalDate.now().getDayOfMonth() == 1) return;
36253 amit 48
 
49
        List<ImeiActivationTimestampModel> imeisActivationList = activatedImeiRepository
36580 amit 50
                .selectImeiActivationPendingByBrand("Vivo", 4);
36253 amit 51
        LOGGER.info("Secondary imeisActivationList = {}, size = {}", imeisActivationList, imeisActivationList.size());
52
 
53
        processImeis(imeisActivationList);
54
    }
55
 
36260 amit 56
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
36253 amit 57
    public void checkImeiActivationTertiary() throws Exception {
58
        if (LocalDate.now().getDayOfMonth() == 1) return;
59
 
60
        List<ImeiActivationTimestampModel> imeisActivationList = activatedImeiRepository
61
                .selectImeiActivationPendingByBrandTertiary("Vivo", 2);
62
        LOGGER.info("Tertiary imeisActivationList = {}, size = {}", imeisActivationList, imeisActivationList.size());
63
 
64
        processImeis(imeisActivationList);
65
    }
66
 
67
    private void processImeis(List<ImeiActivationTimestampModel> imeisActivationList) throws Exception {
68
        if (imeisActivationList.isEmpty()) return;
69
 
36420 amit 70
        BasicCookieStore cookieStore = new BasicCookieStore();
71
        try (CloseableHttpClient httpClient = HttpClientFactory.apacheHttpWithCookies(cookieStore)) {
72
            // Seed the session cookies
73
            HttpGet seed = new HttpGet("https://www.vivo.com/in/support/IMEI");
74
            try (CloseableHttpResponse seedResponse = httpClient.execute(seed)) {
75
                EntityUtils.consumeQuietly(seedResponse.getEntity());
33453 amit.gupta 76
            }
29466 amit.gupta 77
 
36420 amit 78
            for (ImeiActivationTimestampModel imeiActivationTimestampModel : imeisActivationList) {
37408 amit 79
                // Never spend a captcha on a line item with no IMEI. Vivo answers
80
                // those with {"msg":"参数为空"} ("parameter is empty") and status 0,
81
                // which this code used to record as a captcha failure -- so a correct
82
                // code looked wrong, the row was never saved, and it came back every
83
                // 5 minutes forever. That was 76% of all rejections.
84
                String serial = imeiActivationTimestampModel.getSerialNumber();
85
                if (serial == null || serial.trim().isEmpty()) {
86
                    LOGGER.warn("Skipping activation lookup: line item has no serial number");
87
                    continue;
88
                }
89
 
37402 amit 90
                Captcha captcha = this.getNewCaptcha(httpClient, cookieStore);
91
                if (captcha == null) {
37393 amit 92
                    // Solver returned something that is not a captcha code (it was down
93
                    // for 46 days returning an nginx 502 page, which used to be submitted
94
                    // to Vivo verbatim). Skip; this IMEI is picked up on the next run.
95
                    continue;
96
                }
37407 amit 97
                this.getImeiActivation(captcha, httpClient, cookieStore, imeiActivationTimestampModel);
33453 amit.gupta 98
            }
36253 amit 99
        }
33453 amit.gupta 100
    }
29451 manish 101
 
37402 amit 102
    /** A solved captcha: the code, plus the image it came from so the verdict can be reported. */
103
    static final class Captcha {
104
        final String code;
105
        final byte[] image;
37407 amit 106
        final long fetchedAt;
29451 manish 107
 
37402 amit 108
        Captcha(String code, byte[] image) {
109
            this.code = code;
110
            this.image = image;
37407 amit 111
            this.fetchedAt = System.currentTimeMillis();
37402 amit 112
        }
113
    }
114
 
115
    public Captcha getNewCaptcha(CloseableHttpClient httpClient, BasicCookieStore cookieStore) throws Exception {
116
        HttpGet request = new HttpGet("https://www.vivo.com/in/support/generatingCodes");
117
 
118
        // Kept in memory rather than written to a shared /tmp/captcha.jpg: the two
119
        // Vivo jobs run concurrently and used to overwrite each other's image. The
120
        // bytes are needed for the verdict report anyway.
121
        byte[] bytes;
36420 amit 122
        try (CloseableHttpResponse response = httpClient.execute(request)) {
37402 amit 123
            bytes = EntityUtils.toByteArray(response.getEntity());
36253 amit 124
        }
29451 manish 125
 
37402 amit 126
        String code = captchaService.getCaptchaCode(bytes);
127
        return code == null ? null : new Captcha(code, bytes);
33453 amit.gupta 128
    }
29451 manish 129
 
37402 amit 130
    public void getImeiActivation(Captcha captcha, CloseableHttpClient httpClient,
37407 amit 131
                                  BasicCookieStore cookieStore,
33453 amit.gupta 132
                                  ImeiActivationTimestampModel vivoImeiAndActivationTimeStampModel) throws Exception {
29451 manish 133
 
33453 amit.gupta 134
        String imei = vivoImeiAndActivationTimeStampModel.getSerialNumber();
29466 amit.gupta 135
 
36420 amit 136
        List<NameValuePair> form = new ArrayList<>();
137
        form.add(new BasicNameValuePair("imei", imei));
37402 amit 138
        form.add(new BasicNameValuePair("code", captcha.code));
36420 amit 139
        HttpPost post = new HttpPost("https://www.vivo.com/in/support/checkCode");
140
        post.setEntity(new UrlEncodedFormEntity(form, StandardCharsets.UTF_8));
29466 amit.gupta 141
 
36253 amit 142
        JSONObject imeiActivationJson;
36420 amit 143
        try (CloseableHttpResponse response2 = httpClient.execute(post)) {
144
            imeiActivationJson = new JSONObject(EntityUtils.toString(response2.getEntity()));
36253 amit 145
        }
146
 
33453 amit.gupta 147
        JSONObject data = imeiActivationJson.getJSONObject("data");
148
        int status = data.getInt("status");
37402 amit 149
 
150
        // Vivo is the only judge of whether our guess was right; status 0 means the
151
        // captcha was wrong. Hand that back so the sample becomes training data.
152
        captchaService.reportVerdict(captcha.image, captcha.code, status != 0);
153
 
33453 amit.gupta 154
        if (status == 0) {
37407 amit 155
            // Vivo refuses ~56% of submissions in production, but reading the stored
156
            // images by hand showed 9 of 10 carried the CORRECT code, and no external
157
            // replication reproduces it (IMEI, user-agent, session reuse, concurrency,
158
            // source IP and submit delay were all measured and ruled out). The old log
159
            // line recorded five words and discarded the evidence, so capture it:
160
            // the code submitted, the imei, how long fetch->submit actually took inside
161
            // this process, the session cookies held at submit time, and Vivo's reply.
162
            LOGGER.info("Vivo rejected captcha code={} imei={} elapsedMs={} cookies={} response={}",
163
                    captcha.code, imei, System.currentTimeMillis() - captcha.fetchedAt,
164
                    cookieStore.getCookies(), imeiActivationJson);
33453 amit.gupta 165
            return;
166
        } else if (status == 2) {
167
            LOGGER.info("Received status 2 for {}, data {}", imei, data);
36253 amit 168
            activatedImeiRepository.saveActivation(imei, null);
33453 amit.gupta 169
            return;
170
        }
29451 manish 171
 
33453 amit.gupta 172
        JSONObject imeiQueryDto = data.getJSONObject("imeiQueryDto");
173
        LOGGER.info("imeiQueryDto - {}", imeiQueryDto);
174
        String dueTimeString = imeiQueryDto.getString("dueTime");
29466 amit.gupta 175
 
36253 amit 176
        if (dueTimeString.equals("")) {
177
            activatedImeiRepository.saveActivation(imei, null);
178
            return;
33453 amit.gupta 179
        }
29469 amit.gupta 180
 
33453 amit.gupta 181
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy");
182
        dueTimeString = dueTimeString.split(" ")[0];
183
        LocalDate dueDate = LocalDate.parse(dueTimeString, formatter);
30343 amit.gupta 184
 
36253 amit 185
        activatedImeiRepository.saveActivation(imei, dueDate.atStartOfDay().minusYears(1));
186
        LOGGER.info("activatedImei saved for {} with dueDate {}", imei, dueDate.atStartOfDay());
33453 amit.gupta 187
    }
36253 amit 188
}