Artikel ini hanya berisi ide dan tidak memberikan implementasi yang spesifik dan lengkap (blogger terlalu malas untuk menyelesaikannya). Jika Anda memiliki pertanyaan atau ingin tahu, Anda dapat mengirim pesan atau komentar pribadi.
latar belakang
Dalam proyek tradisional Java Web kecil dan menengah, sesi umumnya digunakan untuk sementara menyimpan informasi sesi, seperti informasi identitas penebang. Mekanisme ini diimplementasikan dengan meminjam mekanisme cookie HTTP, tetapi lebih merepotkan bagi aplikasi untuk menyimpan dan berbagi informasi cookie setiap kali meminta, dan sesi tradisional tidak ramah cluster, sehingga layanan backend aplikasi umum menggunakan token untuk membedakan informasi login pengguna.
Semua orang tahu mekanisme sesi J2EE, yang sangat nyaman digunakan dan sangat berguna dalam aplikasi web Java tradisional. Namun, beberapa proyek yang dapat digunakan dalam proyek internet atau kelompok memiliki beberapa masalah, seperti masalah serialisasi, masalah penundaan sinkronisasi, dll., Jadi kami memerlukan alat yang dapat menyelesaikan masalah cluster yang mirip dengan sesi.
rencana
Kami menggunakan mekanisme cache untuk menyelesaikan masalah ini. Redis yang lebih populer adalah database memori NoSQL dan memiliki mekanisme kegagalan cache, yang sangat cocok untuk menyimpan data sesi. String token perlu dikembalikan ke klien pada permintaan pertama, dan klien menggunakan token ini untuk mengidentifikasi identitas setiap kali ia meminta di masa depan. Agar transparan tentang pengembangan bisnis, kami merangkum paket yang dibuat oleh permintaan dan tanggapan aplikasi. Kami hanya perlu melakukan beberapa trik ke kelas alat permintaan HTTP klien dan kerangka kerja MVC server. Modifikasi kelas alat HTTP klien sangat sederhana, terutama enkapsulasi protokol server.
Ide Implementasi
1. Merumuskan protokol pesan respons permintaan.
2. Protokol parsing memproses string token.
3. Gunakan Redis untuk menyimpan untuk mengelola token dan informasi sesi yang sesuai.
4. Berikan API untuk menyimpan dan mendapatkan informasi sesi.
Kami akan menjelaskan rencana implementasi setiap langkah langkah.
1. Merumuskan protokol pesan respons permintaan.
Karena Anda ingin merangkum protokol pesan, Anda perlu mempertimbangkan apa itu bidang publik, apa itu bidang layanan, struktur data pesan, dll.
Bidang publik yang diminta umumnya termasuk token, versi, platform, model, imei, sumber aplikasi, dll., Di antaranya token mana yang menjadi protagonis saat ini.
Bidang umum dari respons umumnya termasuk token, status hasil (keberhasilan, gagal), kode hasil (kode), informasi hasil, dll.
Untuk struktur data paket, kami memilih JSON karena JSON adalah umum, memiliki visualisasi yang baik, dan memiliki hunian byte rendah.
Pesan permintaan adalah sebagai berikut, dan badan menyimpan informasi bisnis, seperti nama pengguna dan kata sandi yang masuk, dll.
{"token": "token klien", / ** Nomor Versi Bangun Klien* / "Versi": 11, / ** Platform Klien Tipe* / "Platform": "iOS", / ** Model perangkat klien* / "Machinemodel": "iPhone 6s", "{" "" Nomor Kunci (Ponsel) ", / ** Badan Pesan 2" "" "" "" " "key21": "value21"}, "key3": [1,]}}Pesan responsif
{ / ** Sukses* / "Sukses": false, / ** Setiap permintaan akan kembali ke token, dan klien harus menggunakan token terbaru untuk setiap permintaan* / "token": "server memilih token untuk permintaan saat ini", / ** Kode Gagal* / "FailCode": 1, / ** Pesan Bisnis yang Adalah atau Pesan Kegagalan* / "msg": "tidak diketahui Penyebab apa pun" }}2. Protokol parsing memproses string token.
Untuk kerangka kerja MVC sisi server, kami menggunakan kerangka kerja SpringMVC, yang juga umum dan tidak akan dijelaskan.
Jangan sebutkan pemrosesan token untuk saat ini. Pertama, cara melewati parameter setelah merumuskan paket.
Karena informasi permintaan dienkapsulasi, agar kerangka kerja SpringMVC untuk menyuntikkan parameter yang kita butuhkan dengan benar di pengontrol, kita perlu menguraikan dan mengonversi paket.
Untuk menguraikan informasi permintaan, kita perlu menyesuaikan konverter parameter SpringMVC. Dengan mengimplementasikan antarmuka handlermethodargumentresolver, kami dapat mendefinisikan konverter parameter
RequestBodyResolver mengimplementasikan metode resolveargument dan menyuntikkan parameter. Kode berikut adalah kode sampel, dan tidak menggunakannya secara langsung.
@Override Objek publik ResolVeARGUMEN (parameter MethodParameter, ModelAndViewContainer MavContainer, NativeWebRequest WebRequest, WebDatabinderFactory BinderFactory) melempar Exception {String requestbodystr = webRequest.getParameter) Metode Pesan Anda, Anda akan meminta Anda, Anda akan meminta webRequest. bisa mendapatkannya jika (stringutils.isnotblank (requestBodystr)) {string paramname = parameter.getParametername (); // Dapatkan nama parameter di kelas pengontrol <?> paramClass = parameter.getParameterpe (); // get Parameter dalam pengontrol/* Paket Parses melalui JSON Class*/Jsonnes (JSONNEDEPEPER) JSOPREPREDE. if (paramclass.equals (serviceSequest.class)) {// serviceSequest adalah vo yang sesuai dengan paket permintaan servicerequest serviceSequest = objectMapper.readValue (jsonnode.traverse (), serviceSequest.class); kembalikan serviceSequest; // kembalikan objek ini untuk menyuntikkan ke dalam parameter, itu harus sesuai dengan tipe, jika tidak, pengecualian tidak akan mudah ditangkap} if (jsonnode! = null) {// Temukan parameter yang diperlukan dalam pengontrol dari pesan jsonnode paramjsonnode = jsonnode.findValue (paramName); if (paramJsonnode! = null) {return ObjectMapper.readValue (paramJsonnode.traverse (), paramClass); }}} return null; }Konfigurasikan konverter parameter yang Anda tentukan ke dalam file konfigurasi srpingmvc <mvc: argumen-resolvers>
<mvc: argumen-resolvers> <!-Pemrosesan informasi permintaan terpadu, mengambil data dari serviceSequest-> <bean id = "requestBodyResolver"> <name properti = "ObjectMapper"> <Bean> </temuks. name = "requestBodyParamName"> <value> requestbody </ value> </pruptent> </tean> </mvc: argumen-resolvers>
Ini memungkinkan parameter dalam pesan diidentifikasi dengan benar oleh SpringMVC.
Selanjutnya kita harus memproses token. Kita perlu menambahkan interseptor SRPingMVC untuk mencegat setiap permintaan. Ini adalah fungsi umum dan tidak akan dijelaskan secara rinci.
Pencocokan m1 = pola. if (m1.find ()) {token = m1.group (1);} tokenmappool.verifyToken (token); // lakukan pemrosesan publik token dan verifikasiDengan cara ini, Anda bisa mendapatkan token dan Anda bisa melakukan pemrosesan publik.
3. Gunakan Redis untuk menyimpan untuk mengelola token dan informasi sesi yang sesuai.
Bahkan, itu hanya menulis kelas alat operasi Redis. Karena Spring digunakan sebagai kerangka utama proyek, dan kami tidak menggunakan banyak fungsi Redis, kami langsung menggunakan fungsi Cachemanager yang disediakan oleh Spring.
Konfigurasikan org.springframework.data.redis.cache.rediscachemanager
<!-- Cache Manager global variables, etc. can be used to access --><bean id="cacheManager"> <constructor-arg> <ref bean="redisTemplate"/> </constructor-arg> <property name="usePrefix" value="true" /> <property name="cachePrefix"> <bean> <constructor-arg name="delimiter" value=":@WebServiceInterface"/> </bean> </property> <property name="expires"><!-- Cache Validity Period--> <map> <entry> <key><value>tokenPoolCache</value></key><!-- tokenPool cache name--> <value>2592000</value><!-- Valid time--> </entry> </map> </property></bean>
4. Berikan API untuk menyimpan dan mendapatkan informasi sesi.
Melalui pemanasan di atas, kami hampir memproses token. Selanjutnya, kami akan menerapkan pekerjaan manajemen token.
Kita perlu membuat pengembangan bisnis nyaman untuk menyimpan dan mendapatkan informasi sesi, dan token transparan.
Impor java.util.hashmap; import java.util.map; impor org.apache.commons.logging.log; impor org.apache.commons.logging.logfactory; import org.springframework.cache.cache; impor org.springframework.cache.cachepapper.cache; org.springframework.cache.Cache.ValueWrapper;import org.springframework.cache.CacheManager;/** * * Class name: TokenMapPoolBean * Description: token and related information call processing class* Modification record: * @version V1.0 * @date April 22, 2016* @author NiuXZ * */public class TokenMapPoolBean { private static final Log log = logfactory.getLog (tokenMappoolBean.class); / ** Token yang sesuai dengan permintaan saat ini*/ thread private <string> currentToken; Private Cachemanager Cachemanager; Private String Cachename; Tokengenerator pribadi; Public TokenMappoolbean (Cachemanager Cachemanager, String Cachename, Tokengenerator Tokengenerator) {this.cachemanager = CacheManager; this.cachename = Cachename; this.tokengenerator = tokengenerator; currentToken = threadlocal baru <string> (); } /*** Jika token legal, kembalikan token. Buat token baru dan kembalikan, * Letakkan token di threadlocal dan inisialisasi tokenMap * @param token * @return token */ public string verifyToken (string token) {// log.info("CheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheC heCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheC VerifikasiedToken = null; if (tokengenerator.checkTokenFormat (token)) {// log.info ("checkToken berhasil:/"+token+"/" "); verifikasiedtoken = token;} else {verifyedtoken = newtoken ();} curriceToken (verified ifeCeCoke; cubeake = cubeake = cubeake = cubeake = cubeake = cubeAgeCEK ();} CurrentToken (verifiFiFied (verifiFiCEKECOKER = COCHEKE (); COCACHEK (); COCACHEK (); COCACHEK (); COCACHEK (); COCACHEK (); COCACHEK (); ifeCAGECOKED (); (cache == NULL) {Throw runimeException baru ("Cache Pool Where Token tidak dapat diperoleh, Chachename:" + cacheename); VerifikasiedToken = newToken (); Cachemanager.getCache (Cachename); ! = NULL); // log.info ("Buat token berhasil:/"+newtoken+"/" Cobalah untuk menghasilkan: "+hitung+" kali "); return newtoken;}/** * Dapatkan objek dari KUNCI PUBLIK DALAM KEBITUNG KEPERTIMBAH INI TOKEN DARI TOKEN DARI SAAT INI {Cache Cache = CacheManager.getCache (CacheName); (TokenMapWrapper! = NULL) {TokenMap = (Peta <String, Object>) TokenMapWrapper.get ()} if (TokenMap == NULL) {verifyToken (CurrentToken.get (); TokenMap.get (Key); Seluruh TokenMap setiap saat. ValueWrapper TokenMapWrapper = Cache.get (Surrestoken.get ()); Cache.get (Surraftoken.get ()); Thread saat ini * Token RETNURE */PUBLIK GetToken () {if (currentToken.get () == null) {// menginisialisasi token setelah memverifikasiToken (null); Cache = Cachemanager.getCache (Cachename); SetCachemanager (Cachemanager Cachemanager) {this.cachemanager = Cachemanager} getCachename () {return cachename; setokengenator (tokengenerator tokengenerator) {this.tokengenerator = tokengenerator;Variabel threadlocal digunakan di sini karena permintaan sesuai dengan utas dalam wadah servlet, dan berada di utas yang sama selama siklus hidup permintaan, dan beberapa utas berbagi token Manager pada saat yang sama, sehingga variabel lokal utas ini diperlukan untuk menyimpan string token.
Catatan:
1. Metode Panggilan untuk MemverifikasiToken harus dipanggil pada awal setiap permintaan. Dan setelah permintaan selesai, jelas dipanggil untuk membersihkan, agar tidak menyebabkan verifikasi yang dieksekusi lain kali, tetapi token dikeluarkan dari Threadlocal ketika kembali. (Bug ini telah mengganggu saya selama beberapa hari, dan kode cek pengembangan N tidak ditemukan. Akhirnya, setelah pengujian, saya menemukan bahwa pencegat tidak dimasukkan ketika 404 terjadi, jadi saya tidak menyebut metode VerificationToken, yang menyebabkan token dalam informasi pengecualian yang dikembalikan untuk menjadi token yang diminta) yang menghasilkan masalah besar.
2. Klien harus menyimpan setiap token saat merangkum alat HTTP dan menggunakannya untuk permintaan berikutnya. Pengembangan iOS perusahaan meminta outsourcing, tetapi outsourcing tidak berlaku sesuai kebutuhan. Saat tidak masuk, token tidak disimpan. Setiap kali token dilewati, itu adalah nol, menghasilkan token yang dibuat untuk setiap permintaan, dan server membuat sejumlah besar token yang tidak berguna.
menggunakan
Metode penggunaan juga sangat sederhana. Berikut ini adalah manajer login yang dienkapsulasi. Anda dapat merujuk pada aplikasi Token Manager untuk Login Manager.
impor org.apache.commons.logging.log; impor org.apache.commons.logging.logfactory; impor org.springframework.cache.cache; impor org.springframework.cache.cache.valuewrapper; impor org.springframework.cache.cache.valueWrapper; impor org.springframework.cache.cache.cache.valueWrapper; Nama kelas: LoginManager * Deskripsi: LoginManager * Modifikasi Catatan: * @Version v1.0 * @Date 19 Juli 2016 * @Author Niuxz * */Kelas Publik LoginManager {log private static log akhir = logfactory.getLog (LoginManager.class); Private Cachemanager Cachemanager; Private String Cachename; Tokenmappoolbean Tokenmappool Private; Public LoginManager (CacheManager Cachemanager, String Cachename, TokenMappoolbean TokenMappool) {this.cachemanager = cachemanager; this.cachename = Cachename; this.tokenMappool = tokenMappool; } Login public void (String userId) {log.info ("Login pengguna: userid =" + userId); Cache cache = cacheManager.getCache (cacheename); Valuewrapper valuewrapper = cache.get (userid); String token = (string) (valuewrapper == null? Null: valuewrapper.get ()); tokenMappool.removetokenMap (token); // catatan login sebelum keluar tokenMappool.setAttribute (constants.logged_user_id, userid); cache.put (userid, tokenmappool.getToken ()); } public void logoutCurrent (string phonetel) {string curuserId = getCurrentUserId (); log.info ("logout pengguna: userid =" + curuserId); tokenMappool.removetokenMap (tokenMappool.getToken ()); // login if (curuserid! = null) {cache cache = cacheManager.getCache (cacheName); cache.evict (curuserid); cache.evict (fonetel); }} / ** * Dapatkan ID dari pengguna saat ini * @return * / public string getCurrentUserId () {return (string) tokenMappool.getAttribute (constants.logged_user_id); } public cacheManager getCacheManager () {return cacheManager; } public String getCachename () {return cachename; } public TokenMappoolBean gettokenMappool () {return tokenmappool; } public void setCacheManager (Cachemanager Cachemanager) {this.cachemanager = cachemanager; } public void setCachename (String Cachename) {this.cachename = Cachename; } public void SettokenMappool (tokenMappoolBean tokenMappool) {this.tokenMappool = tokenMappool; }}Di bawah ini adalah antarmuka kode verifikasi SMS umum. Beberapa aplikasi juga menggunakan sesi untuk menyimpan kode verifikasi. Saya tidak merekomendasikan menggunakan metode ini. Kerugian dari sesi penyimpanan cukup besar. Lihat saja, bukan itu yang saya tulis
public void sendValicodeByPhonenum (String phonenum, string hintmsg, string logsuffix) {validatePhonetimesPace (); // Dapatkan kode string nomor 6-bit acak = codeutil.getValidateCode (); log.info (kode + "------>" + phonenum); // Panggil kode verifikasi SMS untuk mengirim antarmuka retstatus retstatus = msgsendutils.sendsms (kode + hintmsg, phonenum); if (! retstatus.getisok ()) {log.info (retstatus.toString ()); lempar throwstodataException baru (serviceresponsecode.fail_invalid_params, "Kode verifikasi ponsel telah gagal untuk mendapatkan, silakan coba lagi nanti"); } // Reset sesi tokenMappool.setAttribute (constants.validate_phone, phonenum); tokenMappool.setAttribute (constants.validate_phone_code, code.toString ()); tokenMappool.setAttribute (constants.send_code_wrongnu, 0); tokenMappool.setAttribute (constants.send_code_time, date baru (). getTime ()); log.info (logSuffix + phonenum + "kode verifikasi SMS:" + kode); }Respons pemrosesan
Beberapa siswa akan bertanya apakah ada kemasan pesan yang responsif?
@RequestMapping ("Record")@ResponseBodyPublic Serviceresponse Record (pesan string) {String userId = LOGINMANAGER.GetCurrentUserId (); MessageBoardService.RecordMessage (userid, pesan); kembalikan serviceresponsebuilder.buildsuccess (null);}Di antara mereka, Serviceresponse adalah paket respons yang dienkapsulasi. Kami hanya perlu menggunakan anotasi @ResponseBody dari SpringMVC. Kuncinya adalah pembangun ini.
impor org.apache.commons.lang3.stringutils; import com.niuxz.base.pojo.serviceresponse; com.niuxz.utils.spring.springcontextutil; com.niuxz.web.server.token.tokenmappoolbean; V1.0 * @Date 25 April 2016 * @Author niuxz * */Public Class ServiceSesponsebuilder {/** * Bangun pesan respons yang sukses * * @param body * @return A Serviceresponse dengan Operasi yang berhasil */Public Staticeresponse (BodyBon (Object Body) {Return Servicerpon (Object Body) {Return Servicerpon Public StaticerSponse (Object Body) {Return Servicerpon New Servicerpon (Object Body) Springcontextutil.getbean ("tokenmappool")) .getToken (), "aksi keberhasilan", tubuh); } / ** * Bangun pesan respons yang berhasil * * @param body * @return A Servicerespons dengan operasi yang berhasil * / Public Static ServiceResponse BuildSuccess (Token String, Badan Objek) {return New Serviceresponse (Token, "Action Success", Body); } / ** * Bangun pesan respons yang gagal * * @param failcode * msg * @return A serviceresponse yang gagal operasi * / public static serviceresponse buildfail (int failcode, string msg) {return buildfail (failcode, msg, null); } /** * Build a failed response message* * @param failCode * msg body * @return A ServiceResponse that failed operation */ public static ServiceResponse buildFail(int failCode, String msg, Object body) { return new ServiceResponse( ((TokenMapPoolBean) SpringContextUtil.getBean("tokenMapPool")) .getToken(), failCode, Stringutils.isnotblank (msg)? }}Karena kami menggunakan bentuk kelas alat statis, kami tidak dapat menyuntikkan objek tokenMappool (Token Manager) melalui pegas, dan kemudian kami dapat memperolehnya melalui API yang disediakan oleh Spring. Kemudian, saat membangun informasi respons, langsung hubungi metode gettoken () dari tokenMappool. Metode ini akan mengembalikan string token yang diikat oleh utas saat ini. Sekali lagi, penting untuk memanggil jelas secara manual setelah permintaan selesai (saya menyebutnya melalui Interceptor Global).
Contoh manajemen informasi sesi backend aplikasi di atas yang meniru mekanisme sesi J2EE adalah semua konten yang saya bagikan dengan Anda. Saya harap Anda dapat memberi Anda referensi dan saya harap Anda dapat mendukung wulin.com lebih lanjut.