Latar Belakang
Proyek perusahaan memiliki persyaratan. Front-end mengunggah file excel, back-end membaca data, memproses data, dan mengembalikan data kesalahan. Cara termudah adalah menyinkronkan pemrosesan. Setelah mengunggah file, klien telah diblokir menunggu tanggapan, tetapi pengalaman pengguna tidak diragukan lagi buruk, memproses data Ini mungkin sangat memakan waktu, tidak ada yang mau menunggu, karena proyek belum menggunakan ActiveMQ dan middleware antrian pesan lainnya, dan lpush dan rpop redis sangat cocok sebagai implementasi antrian pesan yang ringan, jadi gunakan untuk menyelesaikan pengembangan fungsi
1. Artikel ini melibatkan poin-poin pengetahuan
- Membaca dan menulis file Excel - Ali easyexcel sdk
- Unggah dan unduh file - Tencent Cloud Object Storage
- Panggilan layanan jarak jauh - restTemplate
- Produser, konsumen - operasi redisTemplate leftPush dan rightPop
- Pemrosesan data-kumpulan thread Pelaksana asinkron
- Baca aliran file jaringan - HttpClient
- Anotasi khusus untuk mencapai otentikasi identitas pengguna - Autentikasi token JWT, interseptor memotong entri permintaan yang ditandai dengan @LoginRequired annotation
Tentu saja, implementasi Java
Ada banyak poin pengetahuan yang terlibat, dan setiap poin pengetahuan dapat digunakan sebagai topik untuk pembelajaran dan analisis. Artikel ini akan memaparkan realisasinya secara lengkap, dan kemudian membagi dan berbagi pembelajaran dengan teman-teman.
Kedua, struktur direktori proyek
Catatan: Lapisan DAO database ditempatkan di modul lain, yang bukan fokus dari artikel ini
Tiga, ketergantungan pakar utama
1.easyexcel
< easyexcel-latestVersion > 1.1.2-beta4 < / easyexcel-latestVersion > < ketergantungan > < groupId > com.alibaba < / groupId > < artifactId > easyexcel < / artifactId > < Versi: kapan > $ {easyexcel-latestVersion} < /Versi: kapan > < /ketergantungan >2. JWT
< ketergantungan > < groupId > io.jsonwebtoken < / groupId > < artifactId > jjwt < / artifactId > < Versi: kapan > 0.7.0 < /Versi: kapan > < /ketergantungan >3. redis
< ketergantungan > < groupId > org.springframework.boot < / groupId > < artifactId > pegas-boot-starter-redis < / artifactId > < Versi: kapan > 1.3.5. RILIS < /Versi: kapan > < /ketergantungan >4. Tencent cos
< ketergantungan > < groupId > com.qcloud < / groupId > < artifactId > cos_api < / artifactId > < Versi: kapan > 5.4.5 < /Versi: kapan > < /ketergantungan >Keempat, proses
- File unggahan pengguna
- Simpan file ke Tencent cos
- Simpan id file yang diupload dan upload record ke database
- redis menghasilkan pesan impor, yaitu menyimpan id file ke redis
- Saat permintaan selesai, kembali ke status "Memproses"
- redis berita konsumsi
- Baca file cos, proses data secara asynchronous
- Unggah data kesalahan ke cos dalam bentuk excel untuk diunduh pengguna, dan perbarui status pemrosesan menjadi "pemrosesan selesai"
- Klien memeriksa status pemrosesan kueri, dan dapat mengunduh file kesalahan
- Akhir
Lima, raih efeknya
1. Unggah file
2. Catatan impor database
3. Data yang diimpor
4. Download file yang salah
5. Perintah data salah
6. Permintaan catatan impor
Enam, implementasi kode
1. Impor lapisan kontrol excel
@Login dibutuhkan @RequestMapping (nilai = "doImport", metode = RequestMethod.POST) publicJsonResponsedoImport (@RequestParam ("file") MultipartFilefile, HttpServletRequestrequest) { PLUseruser = getUser (permintaan); returnorderImportService.doImport (file, user.getId ()); }2. Lapisan layanan
@Mengesampingkan publicJsonResponsedoImport (MultipartFilefile, IntegeruserId) { jika (null == file || file.isEmpty ()) { thrownewServiceException ("File tidak boleh kosong"); } Stringfilename = file.getOriginalFilename (); if (! checkFileSuffix (nama file)) { thrownewServiceException ("Saat ini hanya mendukung excel dalam format xlsx"); } // Simpan file StringfileId = saveToOss (file); if (StringUtils.isBlank (fileId)) { thrownewServiceException ("Unggahan file gagal, coba lagi nanti"); } // Simpan record ke database saveRecordToDB (ID pengguna, ID file, nama file); // Buat pesan impor pesanan redisProducer.produce (RedisKey.orderImportKey, fileId); returnJsonResponse.ok ("Impor berhasil, memproses ..."); } / ** * Verifikasi format file * @ paramileName *@kembali * / privatestaticbooleancheckFileSuffix (StringfileName) { if (StringUtils.isBlank (fileName) || fileName.lastIndexOf (".") < = 0) { returnfalse; } intpointIndex = fileName.lastIndexOf ("."); Stringsuffix = fileName.substring (pointIndex, fileName.length ()). ToLowerCase (); if (". xlsx" .equals (akhiran)) { returntrue; } returnfalse; } / ** * Simpan file ke Tencent OSS * @ paramile *@kembali * / privateStringsaveToOss (MultipartFilefile) { InputStreamins = null; mencoba{ ins = file.getInputStream (); } tangkap (IOExceptione) { e.printStackTrace (); } StringfileId; mencoba{ StringoriginalFilename = file.getOriginalFilename (); Filef = newFile (originalFilename); inputStreamToFile (ins, f); FileSystemResourceresource = newFileSystemResource (f); MultiValueMap < String, Objek > param = newLinkedMultiValueMap < > (); param.add ("file", resource); ResponseResultresponseResult = restTemplate.postForObject (txOssUploadUrl, param, ResponseResult.class); fileId = (String) responseResult.getData (); } menangkap (Exceptione) { fileId = null; } returnfileId; }3. Redis produser
@Layanan publicclassRedisProducerImplimplementsRedisProducer { @Autired privateRedisTemplateredisTemplate; @Mengesampingkan publicJsonResponseproduce (Stringkey, Stringmsg) { Peta < String, String > map = Maps.newHashMap (); map.put ("fileId", msg); redisTemplate.opsForList (). leftPush (kunci, peta); returnJsonResponse.ok (); } }4. Redis konsumen
@Layanan publicclassRedisConsumer { @Autired publicRedisTemplateredisTemplate; @Value ("$ {txOssFileUrl}") privateStringtxOssFileUrl; @Value ("$ {txOssUploadUrl}") privateStringtxOssUploadUrl; @Posting publicvoidinit () { processOrderImport (); } / ** * Proses impor pesanan * / privatevoidprocessOrderImport () { ExecutorServiceexecutorService = Executors.newCachedThreadPool (); executorService.execute (() - > { sementara (benar) { Objectobject = redisTemplate.opsForList (). RightPop (RedisKey.orderImportKey, 1, TimeUnit.SECONDS); jika (null == objek) { terus; } Stringmsg = JSON.toJSONString (objek); executorService.execute (newOrderImportTask (msg, txOssFileUrl, txOssUploadUrl)); } }); } }5. Memproses kelas thread tugas
publicclassOrderImportTaskimplementsRunnable { publicOrderImportTask (Stringmsg, StringtxOssFileUrl, StringtxOssUploadUrl) { this.msg = msg; this.txOssFileUrl = txOssFileUrl; this.txOssUploadUrl = txOssUploadUrl; } } / ** * Suntikkan kacang * / privatevoidautowireBean () { this.restTemplate = BeanContext.getApplicationContext (). getBean (RestTemplate.class); this.transactionTemplate = BeanContext.getApplicationContext (). getBean (TransactionTemplate.class); this.orderImportService = BeanContext.getApplicationContext (). getBean (OrderImportService.class); } @Mengesampingkan publicvoidrun () { // Suntikkan kacang autowireBean (); JSONObjectjsonObject = JSON.parseObject (msg); StringfileId = jsonObject.getString ("fileId"); MultiValueMap < String, Objek > param = newLinkedMultiValueMap < > (); param.add ("id", fileId); ResponseResultresponseResult = restTemplate.postForObject (txOssFileUrl, param, ResponseResult.class); StringfileUrl = (String) responseResult.getData (); if (StringUtils.isBlank (fileUrl)) { kembali; } InputStreaminputStream = HttpClientUtil.readFileFromURL (fileUrl); Daftar < Obyek > list = ExcelUtil.read (inputStream); proses (daftar, fileId); } / ** * Unggah file ke oss * @ paramile *@kembali * / privateStringsaveToOss (Filefile) { StringfileId; mencoba{ FileSystemResourceresource = newFileSystemResource (file); MultiValueMap < String, Objek > param = newLinkedMultiValueMap < > (); param.add ("file", resource); ResponseResultresponseResult = restTemplate.postForObject (txOssUploadUrl, param, ResponseResult.class); fileId = (String) responseResult.getData (); } menangkap (Exceptione) { fileId = null; } returnfileId; }Catatan: Tidak perlu memposting kode logika bisnis untuk memproses data
6. Unggah file ke cos
@Requestapping ("/ txOssUpload") @Tokopedia publicResponseResulttxOssUpload (@RequestParam ("file") MultipartFilefile) throwsUnsupportedEncodingException { jika (null == file || file.isEmpty ()) { returnResponseResult.fail ("File tidak boleh kosong"); } StringoriginalFilename = file.getOriginalFilename (); originalFilename = MimeUtility.decodeText (originalFilename); // Selesaikan masalah Cina yang kacau StringcontentType = getContentType (originalFilename); Stringkey; InputStreamins = null; Filef = null; mencoba{ ins = file.getInputStream (); f = newFile (originalFilename); inputStreamToFile (ins, f); key = iFileStorageClient.txOssUpload (newFileInputStream (f), originalFilename, contentType); } menangkap (Exceptione) { returnResponseResult.fail (e.getMessage ()); }akhirnya{ if (null! = in) { mencoba{ ins.close (); } tangkap (IOExceptione) { e.printStackTrace (); } } if (f.exists ()) {// Hapus file sementara f.delete (); } } returnResponseResult.ok (key); } publicstaticvoidinputStreamToFile (InputStreamins, Filefile) { mencoba{ OutputStreamos = newFileOutputStream (file); intbytesRead = 0; bytebuffer = newbyte; while ((bytesRead = ins.read (buffer, 0,8192))! = - 1) { os.write (buffer, 0, bytesRead); } os.close (); ins.close (); } menangkap (Exceptione) { e.printStackTrace (); } } publicStringtxOssUpload (FileInputStreaminputStream, Stringkey, StringcontentType) { key = Uuid.getUuid () + "-" + key; OSSUtil.txOssUpload (inputStream, key, contentType); mencoba{ if (null! = inputStream) { inputStream.close (); } } tangkap (IOExceptione) { e.printStackTrace (); } kunci kembali; } publicstaticvoidtxOssUpload (FileInputStreaminputStream, Stringkey, StringcontentType) { ObjectMetadataobjectMetadata = newObjectMetadata (); mencoba{ intlength = inputStream.available (); objectMetadata.setContentLength (length); } menangkap (Exceptione) { logger.info (e.getMessage ()); } objectMetadata.setContentType (contentType); cosclient.putObject (txbucketName, key, inputStream, objectMetadata); }7. Unduh file
/ ** * Unduh File Cloud Tencent * @ paramresponse * @ paramid *@kembali * / @RequestMapping ("/ txOssDownload") publicObjecttxOssDownload (HttpServletResponseresponse, Stringid) { COSObjectInputStreamcosObjectInputStream = iFileStorageClient.txOssDownload (id, respon); StringcontentType = getContentType (id); FileUtil.txOssDownload (respons, contentType, cosObjectInputStream, id); returnnull; } publicstaticvoidtxOssDownload (HttpServletResponseresponse, StringcontentType, InputStreamfileStream, StringfileName) { FileOutputStreamfos = null; response.reset (); OutputStreamos = null; mencoba{ response.setContentType (contentType + "; charset = utf-8"); if (! contentType.equals (PlConstans.FileContentType.image)) { mencoba{ response.setHeader ("Content-Disposition", "attachment; filename =" + newString (fileName.getBytes ("UTF-8"), "ISO8859-1")); } menangkap (UnsupportedEncodingExceptione) { response.setHeader ("Content-Disposition", "lampiran; namafile =" + namafile); logger.error ("encodingfilenamefailed", e); } } os = response.getOutputStream (); byteb = newbyte; intlen; sementara ((len = fileStream.read (b)) > 0) { os.write (b, 0, len); os.flush (); mencoba{ if (fos! = null) { fos.write (b, 0, len); fos.flush (); } } menangkap (Exceptione) { logger.error (e.getMessage ()); } } } tangkap (IOExceptione) { IOUtils.closeQuietly (fos); fos = nol; }akhirnya{ IOUtils.closeQuietly (os); IOUtils.closeQuietly (fileStream); if (fos! = null) { IOUtils.closeQuietly (fos); } } }8. Baca aliran file jaringan
/ ** * Baca aliran file jaringan * @ paramurl *@kembali * / publicstaticInputStreamreadFileFromURL (Stringurl) { if (StringUtils.isBlank (url)) { returnnull; } HttpClienthttpClient = newDefaultHttpClient (); HttpGetmethodGet = newHttpGet (url); mencoba{ HttpResponseresponse = httpClient.execute (methodGet); if (response.getStatusLine (). getStatusCode () == 200) { HttpEntityentity = response.getEntity (); returnentity.getContent (); } } menangkap (Exceptione) { e.printStackTrace (); } returnnull; }9, ExcelUtil
/ ** * Baca excel * @ aliran masukan file paraminputStream * @ koleksi daftar kembali * / publicstaticList < Obyek > baca (InputStreaminputStream) { returnEasyExcelFactory.read (inputStream, newSheet (1,1)); } / ** * Tulis excel * @ data paramdatalist * @ paramclazz * @ path penyimpanan file paramsaveFilePath * @ throwsIOException * / publicstaticvoidwrite (Daftar < ? extendsBaseRowModel > data, Kelas < ? extendsBaseRowModel > clazz, StringsaveFilePath) throwsIOException { FiletempFile = newFile (saveFilePath); OutputStreamout = newFileOutputStream (tempFile); ExcelWriterwriter = EasyExcelFactory.getWriter (keluar); Sheetsheet = newSheet (1,3, clazz, "Sheet1", null); writer.write (data, sheet); writer.finish (); out.close (); }Catatan: Pada titik ini, seluruh proses selesai, dan kode poin pengetahuan lainnya juga diposting untuk referensi di bawah ini
Tujuh, lainnya
1. @LoginRequired anotasi
/ ** * Gunakan anotasi ini pada metode Pengontrol yang memerlukan verifikasi masuk * / @Target ({Element.MEMFOD}) @Retensi (Retensi. public @ interfaceLoginRequired { }2. MyControllerAdvice
@DorongAdvice publicclassMyControllerAdvice { @Tokopedia @Exeption (Token Eksepsi) (TokenExepsi.class) publicJsonResponsetokenValidationExceptionHandler () { returnJsonResponse.loginInvalid (); } @Tokopedia @Exeption.class (Service) publicJsonResponseserviceExceptionHandler (ServiceExceptionse) { returnJsonResponse.fail (se.getMsg ()); } @Tokopedia @Exeption.class (Exception.class) publicJsonResponseexceptionHandler (Exceptione) { e.printStackTrace (); returnJsonResponse.fail (e.getMessage ()); } }3. AuthenticationInterceptor
publicclassAuthenticationInterceptorimplementsHandlerInterceptor { privatestaticfinalStringCURRENT_USER = "pengguna"; @Autired privateUserServiceuserService; @Mengesampingkan publicbooleanpreHandle (HttpServletRequestrequest, HttpServletResponseresponse, Objecthandler) { // Jika tidak dipetakan ke metode langsung lulus if (! (handlerinstanceofHandlerMethod)) { returntrue; } HandlerMethodhandlerMethod = (HandlerMethod) penangan; Methodmethod = handlerMethod.getMethod (); // Tentukan apakah antarmuka memiliki anotasi @LoginRequired, jika ada, Anda harus masuk LoginRequiredmethodAnnotation = method.getAnnotation (LoginRequired.class); if (methodAnnotation! = null) { // Verifikasi token IntegeruserId = JwtUtil.verifyToken (permintaan); PLUserplUser = userService.selectByPrimaryKey (userId); if (null == plUser) { thrownewRuntimeException ("Pengguna tidak ada, silakan masuk lagi"); } request.setAttribute (CURRENT_USER, plUser); returntrue; } returntrue; } @Mengesampingkan publicvoidpostHandle (HttpServletRequesthttpServletRequest, HttpServletResponsehttpServletResponse, Objecto, ModelAndViewmodelAndView) throwsException { } @Mengesampingkan publicvoidafterCompletion (HttpServletRequesthttpServletRequest, HttpServletResponsehttpServletResponse, Objecto, Exceptione) throwsException { } }4. JwtUtil
publicstaticfinallongEXPIRATION_TIME = 2592_000_000L; // Masa berlaku adalah 30 hari publicstaticfinalStringSECRET = "pl_token_secret"; publicstaticfinalStringHEADER = "token"; publicstaticfinalStringUSER_ID = "userId"; / ** * Hasilkan token berdasarkan userId * @ paramuserId *@kembali * / publicstaticStringgenerateToken (StringuserId) { HashMap < String, Objek > map = newHashMap < > (); map.put (USER_ID, userId); Stringjwt = Jwts.builder () .setClaims (peta) .setExpiration (newDate (System.currentTimeMillis () + EXPIRATION_TIME)) .signWith (SignatureAlgorithm.HS512, SECRET) .compact (); returnjwt; } / ** * Verifikasi token * @ paramquest * @ kembali diverifikasi dengan mengembalikan userId * / publicstaticIntegerverifyToken (HttpServletRequestrequest) { Stringtoken = request.getHeader (HEADER); if (token! = null) { mencoba{ Peta < String, Objek > body = Jwts.parser () .setSigningKey (SECRET) .parseClaimsJws (token) .getBody (); untuk (Map.Entryentry: body.entrySet ()) { Objectkey = entry.getKey (); Objectvalue = entry.getValue (); if (key.toString (). sama dengan (USER_ID)) { returnInteger.valueOf (value.toString ()); // userId } } returnnull; } menangkap (Exceptione) { logger.error (e.getMessage ()); thrownewTokenValidationException ("unauthorized"); } }lain{ thrownewTokenValidationException ("missingtoken"); } }- Analisis arsitektur akses jaringan ujung ke ujung seluler Ant Financial di bawah tingkat konkurensi 100 juta
- Pemahaman mendalam tentang pemilihan kunci dan kepemimpinan yang didistribusikan Zookeeper berdasarkan Zookeeper