Cet article ne contient que des idées et ne fournit pas une implémentation spécifique et complète (le blogueur est trop paresseux pour le régler). Si vous avez des questions ou si vous souhaitez savoir, vous pouvez envoyer un message privé ou un commentaire.
arrière-plan
Dans les projets traditionnels Java Web et moyens de taille moyenne, la session est généralement utilisée pour stocker temporairement les informations de session, telles que les informations d'identité de l'enregistreur. Ce mécanisme est mis en œuvre en empruntant le mécanisme des cookies de HTTP, mais il est plus difficile pour l'application d'économiser et de partager des informations sur les cookies chaque fois qu'elle demande, et la session traditionnelle n'est pas conviviale, de sorte que les services backend de l'application générale utilisent des jetons pour distinguer les informations de connexion des utilisateurs.
Tout le monde connaît le mécanisme de session de J2EE, qui est très pratique à utiliser et est très utile dans les applications Web Java traditionnelles. Cependant, certains projets qui peuvent être utilisés dans les projets Internet ou les clusters ont des problèmes, tels que des problèmes de sérialisation, des problèmes de retard de synchronisation, etc., nous avons donc besoin d'un outil qui peut résoudre des problèmes de cluster similaires aux sessions.
plan
Nous utilisons le mécanisme de cache pour résoudre ce problème. Le Redis plus populaire est une base de données de mémoire NoSQL et dispose d'un mécanisme de défaillance du cache, qui est très adapté au stockage des données de session. La chaîne de jeton doit être retournée au client lors de la première demande, et le client utilise ce jeton pour identifier l'identité chaque fois qu'il demande à l'avenir. Afin d'être transparent sur le développement commercial, nous encapsulons les paquets fabriqués par la demande et la réponse de l'application. Nous devons seulement faire quelques astuces pour la classe d'outils de demande HTTP du client et le framework MVC du serveur. La modification de la classe d'outils HTTP du client est très simple, principalement l'encapsulation du protocole du serveur.
Idées de mise en œuvre
1. Formuler un protocole de message de réponse de demande.
2. Le protocole d'analyse traite les chaînes de jetons.
3. Utilisez Redis pour stocker pour gérer les jetons et les informations de session correspondantes.
4. Fournir une API pour stocker et obtenir des informations de session.
Nous expliquerons le plan de mise en œuvre de chaque étape.
1. Formuler un protocole de message de réponse de demande.
Étant donné que vous souhaitez encapsuler le protocole de message, vous devez considérer ce qu'est un champ public, quel est un champ de service, la structure de données du message, etc.
Les domaines publics demandés incluent généralement des jetons, de la version, de la plate-forme, du modèle, de l'IMEI, de la source d'application, etc., parmi lesquels le jeton est le protagoniste de notre époque.
Les champs communs de la réponse incluent généralement le jeton, l'état des résultats (succès, l'échec), le code de résultat (code), les informations de résultat, etc.
Pour la structure des données de paquets, nous choisissons JSON car JSON est courant, a une bonne visualisation et a une faible occupation d'octets.
Le message de demande est le suivant, et le corps stocke les informations commerciales, telles que le nom d'utilisateur et le mot de passe connectés, etc.
{"Token": "Client Token", / ** Client build Number * / "version": 11, / ** Client Platform Type * / "Platform": "iOS", / ** Client Device Model * / "MachineModel": "iPhone 6S", "imei": "Client String Number (Mobile Phone)", / ** Message Body2 devrait être map * / "Body": {"Key1": "" {"key21": "value21"}, "key3": [1,]}}Message réactif
{/ ** Success * / "Success": False, / ** Chaque demande reviendra à un jeton, et le client doit utiliser le dernier jeton pour chaque demande * / "Token": "Le serveur sélectionné pour le message commercial ou le message commercial / ** Message de l'échec * /" MSG ":" Cause inonve "corps": null}}2. Le protocole d'analyse traite les chaînes de jetons.
Pour le cadre MVC côté serveur, nous utilisons le cadre SpringMVC, qui est également courant et ne sera pas décrit.
Ne mentionnons pas le traitement des jetons pour le moment. Tout d'abord, comment passer les paramètres après la formulation du paquet.
Étant donné que les informations de demande sont encapsulées, afin que le cadre SpringMVC injecte correctement les paramètres dont nous avons besoin dans le contrôleur, nous devons analyser et convertir les paquets.
Pour analyser les informations de demande, nous devons personnaliser le convertisseur de paramètres de SpringMVC. En mettant en œuvre l'interface HandLerMethodargumentResolver, nous pouvons définir un convertisseur de paramètres
RequestBodyResolver implémente la méthode ResolVeargument et injecte les paramètres. Le code suivant est un exemple de code et ne l'utilisez pas directement.
@Override public Object ResolVeargument (Paramètre méthododine, modéliste, MavContainer MavContainer ModelAnDView if (stringUtils.isnotblank (requestBodyStr)) {string paramname = paramètre.getParametername (); // Obtenir le nom du paramètre dans la classe de contrôleur <?> paramclass = paramètre.getParameterType (); // Obtenir le type de paramètre dans Controller / * PACKATS PACKATS.RIAMENTS JSON TOLLSTR); if (paramclass.equals (ServiceRequest.class)) {// ServiceRequest est le VO correspondant au paquet de demande ServiceRequest ServiceRequest = ObjectMapper.ReadValue (jsonNode.taverse (), ServiceRequest.Class); return ServiceRequest; // renvoie cet objet pour injecter dans le paramètre, il doit correspondre au type, sinon l'exception ne sera pas facilement capturée} if (jsonNode! = null) {// Trouver les paramètres requis dans le contrôleur à partir du message jsonNode paramjsonNode = jsonnode.findvalue (paramname); if (paramjsonNode! = null) {return objectMapper.readValue (paramjsonNode.taverse (), paramclass); }}} return null; }Configurez le convertisseur de paramètres que vous avez défini dans le fichier de configuration SRPingMvc <MVC: Argument-Resolvers>
<MVC: Argument-Resolvers> <! - Unified Request Information Traitement, récupérer les données de ServiceRequest -> <Bean Id = "requestBodyResolver"> <propriété name = "ObjectMapper"> <Eban> </Ean> </ Property> <! - Le nom de champ correspondant à ServiceRequest dans la demande de configuration est requierbody -> <propriété name = "requestParamname"> </ Valuebody> </EAN> </MVC: Argument-Resolvers>
Cela permet aux paramètres du message d'être correctement identifiés par SpringMVC.
Ensuite, nous devons traiter le jeton. Nous devons ajouter un intercepteur Srpingmvc pour intercepter chaque demande. Il s'agit d'une fonction commune et ne sera pas décrite en détail.
Matcher M1 = Pattern.Compile ("/" Token /":/"(.*?)/ ""). Matcher (requestBodystr); if (m1.find ()) {token = m1.group (1);} tokenmappool.verifyToken (token); // effectuer un traitement public de jeton et vérifierDe cette façon, vous pouvez obtenir le jeton et vous pouvez effectuer un traitement public.
3. Utilisez Redis pour stocker pour gérer les jetons et les informations de session correspondantes.
En fait, il s'agit simplement d'écrire une classe d'outils d'opération Redis. Parce que le printemps est utilisé comme cadre principal du projet et que nous n'utilisons pas de nombreuses fonctions de Redis, nous utilisons directement la fonction CacheManager fournie par Spring.
Configurer org.springframework.data.redis.cache.rediscacheManager
<! - Cache Manager Variables globales, etc. peut être utilisée pour accéder -> <bean id = "cacheManager"> <constructor-arg> <ref bean = "redesttemplate" /> </ constructor-arg> <propriété name = "useprefix" value = "true" /> <propriété name = "cacheprefix"> <ean> <constructor-arg name = "Deliliter" Value = ": @ WebServiceInterface" /> </ bean> </ propriété> <propriété name = "Expires"> <! - Cache Validity Période -> <map> <enty> <key> <value> tokenpoolcache </value> </ key
4. Fournir une API pour stocker et obtenir des informations de session.
Grâce aux préliminaires ci-dessus, nous avons presque traité le jeton. Ensuite, nous implémenterons les travaux de gestion des jetons.
Nous devons rendre le développement commercial pratique pour enregistrer et obtenir des informations de session, et les jetons sont transparents.
Importer java.util.hashmap; import java.util.map; import org.apache.commons.logging.log; import org.apache.commons.logging.logfactory; import org.springframework.cache.cache; import org.springframework.cache.valuewrapper; import; org.springframework.cache.cache.valuewrapper; import org.springframework.cache.cacheManager; / ** * * Nom de la classe: TokenMappoolBean * Description: Token et Traitement d'informations connexes Classe * Enregistrement de modification: * @version v1.0 * @date 22 avril 2016 * @Author Niuxz * * Log Final Log = logfactory.getLog (tokenmappoolbean.class); / ** Token correspondant à la demande actuelle * / great privé <string> currentToken; Cachemanager privé Cachemanager; Cachename à chaîne privée; Tokengenerator privé Tokengenerator; Public TokenMappoolBean (CacheManager Cachemanager, String Cachename, Tokengenerator Tokennerator) {this.cacheManager = CacheManager; this.cachename = cachename; this.tokengenerator = tokengenerator; currentToken = new ThreadLocal <string> (); } / ** * Si le jeton est légal, retournez le token. Créez un nouveau jeton et retournez, * Mettez le jeton dans ThreadLocal et initialisez un tokenmap * @param token * @return token * / public String VerifyToken (String token) {// log.info("CheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheCheC HechechechechECHECHECHECHECHECHECHECHECHECHECHECHECHECHECHECHECHECHECHECHECHECHECHECHECHECHECHECHECHECHECHECHECHECHECH VerifyEdToken = null; cacheManager.getCache (cachename); || Value.get () == Null) {VerifyEdToken = NewToken (); {Cache Cache = CacheManager.getCache (Cachename); (cache.get (newtoken)! = null); Session * / public GetAtTribute (String Key) {Cache Cache = CacheManager.getCache (Cachename); tokenmap = null; To-TokenMap (KEY);} / ** * peut ne pas être le dernier, ce qui entraînera une perte de données. Token est stocké, chachename: "+ cachename);} valuewrapper tokenmapwrapper = cache.get (currentToken.get ()); map <string, objet> tokenmap = null; if (tokenmapwrapper! = null) {tokenmap = (map <string, objet>) tokenmapwrapper.get ();} {VerifyToken (CurrentToken.get ()); cache.put (currentToken.get (), tokenmap);} / ** * Obtenez le jeton utilisateur lié au thread actuel * @reTurn Token * / public getToken () {if (currentToken.get () == Null) {// Initialize @param token * / public void removeTokenMap (String token) {if (token == null) {return;} cache cache = cacheManager.getCache ("cachename:" + cachename); Token); this.cachename = cachename;} public tokengenerator getTokengenerator () {return tokengenerator;} public void settogenerator (tokengenerator tokengener {this.tokengenerator = tokengenerator;La variable ThreadLocal est utilisée ici car une demande correspond à un thread dans le conteneur servlet, et il est dans le même thread pendant le cycle de vie d'une demande, et plusieurs threads partagent le gestionnaire de jeton en même temps, de sorte que cette variable locale de fil est nécessaire pour enregistrer la chaîne de jeton.
Notes:
1. L'appel pour vérifier la méthode doit être appelé au début de chaque demande. Et une fois la demande terminée, Clear est appelé à effacer, afin de ne pas provoquer l'exécution de la vérification de la prochaine fois, mais le jeton est retiré du threadlocal lors de son retour. (Ce bug m'a dérangé pendant plusieurs jours, et les codes de contrôle de développement de la société n'ont pas été trouvés. Enfin, après les tests, j'ai constaté que l'intercepteur n'a pas été entré lorsque 404 se sont produits, donc je n'ai pas appelé la méthode VerificationToken, ce qui a provoqué un problème de token dans le problème de l'exception retourné pour être le Big Pot).
2. Le client doit enregistrer chaque jeton lors de l'encapsulation de l'outil HTTP et l'utiliser pour la demande suivante. Le développement iOS de l'entreprise a demandé l'externalisation, mais l'externalisation n'a pas fait si nécessaire. Lorsqu'il n'est pas connecté, le jeton n'est pas sauvé. Chaque fois que le jeton est passé, il est nul, ce qui entraîne un jeton créé pour chaque demande, et le serveur crée un grand nombre de jetons inutiles.
utiliser
La méthode d'utilisation est également très simple. Ce qui suit est le gestionnaire de connexion encapsulé. Vous pouvez vous référer à l'application de Token Manager pour Login Manager.
import org.apache.commons.logging.log; import org.apache.commons.logging.logfactory; import org.springframework.cache.cache; import org.springframework.cache.cache.valuewrapper; import org.springframework.cache.cacheManager; Importer com.niuxz Nom de la classe: LoginManager * Description: LoginManager * Modifier l'enregistrement: * @version v1.0 * @Date 19 juillet 2016 * @author niuxz * * / public class LoginManager {private static final log log = logfactory.getLog (LoginManager.Class); Cachemanager privé Cachemanager; Cachename à chaîne privée; TokenMappoolBean Private TokenMappool; Public LoginManager (Cachemanager Cachemanager, String Cachename, TokenMappoolBean TokenMappool) {this.cacheManager = CacheManager; this.cachename = cachename; this.tokenmappool = tokenmappool; } public void Login (String userId) {log.info ("User Login: userid =" + userId); Cache cache = cacheManager.getCache (cachename); ValueWrapper ValueWrapper = Cache.get (userId); String token = (string) (valuewrapper == null? Null: valuewrapper.get ()); tokenmappool.removetokenmap (token); // enregistrer l'enregistrement avant de vous connecter tokenmappool.setAttribute (constants.logged_user_id, userId); cache.put (userId, tokenmappool.getToken ()); } public void LogoutCurrent (String Phonetel) {String curUserId = getCurrentUserId (); Log.info ("User Logout: Userid =" + CURUSERID); tokenmappool.removetokenmap (tokenmappool.getToken ()); // connecter if (curUserId! = null) {cache cache = cacheManager.getCache (cachename); cache.evict (CURUSERID); cache.evict (phonetel); }} / ** * Obtenez l'ID de l'utilisateur actuel * @return * / public String getCurrentUserrid () {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; }}Vous trouverez ci-dessous une interface de code de vérification SMS commune. Certaines applications utilisent également la session pour stocker les codes de vérification. Je ne recommande pas d'utiliser cette méthode. Les inconvénients des séances de stockage sont assez importants. Regarde-le, ce n'était pas ce que j'ai écrit
public void SendValiCodeByPhonEnum (String Phonenum, String hintmsg, String LogSuffix) {validatePhonEtimeSpace (); // Get 6 bits Random Number String code = codeUtil.getValidateCode (); log.info (code + "------>" + phoneNum); // Appelez le code de vérification SMS pour envoyer l'interface retstatus retstatus = msgSendUtils.sendsms (code + hintmsg, phonenum); if (! retstatus.getSok ()) {log.info (retStatus.ToString ()); Jetez un nouveau ThrowstodataException (ServiceResponseCode.fail_invalid_params, "Le code de vérification du téléphone mobile n'a pas réussi, veuillez réessayer plus tard"); } // Réinitialiser la session 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, new Date (). GetTime ()); log.info (logSuffix + Phonenum + "Code de vérification SMS:" + code); }Réponse de traitement
Certains élèves demanderont s'il y a un tel emballage de messages réactif?
@RequestMapping ("enregistrer") @ réponsebodypublic ServiceResponse Record (message de chaîne) {String userid = loginManager.getCurrentUserrid (); MessageBoardService.recordMessage (userId, message); return ServiceResponseBuilder.BuildSuccess (null);}Parmi eux, la dessertion des services est le paquet de réponse encapsulé VO. Nous avons juste besoin d'utiliser l'annotation @Responsebody de SpringMVC. La clé est ce constructeur.
import org.apache.commons.lang3.stringutils; import com.niuxz.base.pojo.serviceResponse; import com.niuxz.utils.spring.springContextUtil; import com.niuxz.web.server.token.tokenmappoolBean; / ** * nom de classe: ServiceResponseBuilder * * @versev1. @Date 25 avril 2016 * @author niuxz * * / classe publique ServiceResponseBuilder {/ ** * construire un message de réponse réussi * * @param body * @return a ServiceResponse avec une opération réussie * / public static ServiceResponse buildsuccess (objet Body) {return New ServiceResponse (((tokenMappoolBean) SpringContextUtil.getBean ("TokenMappool")) .getToken (), "Action Success", Body); } / ** * Construire un message de réponse réussi * * @param body * @return a ServiceResponse avec une opération réussie * / public statique ServiceResponse buildSuccess (token de chaîne, corps d'objet) {return new ServiceResponse (token, "action réussi", corps); } / ** * build a échoué Message de réponse * * @param failcode * msg * @return a ServiceResponse qui a échoué Opération * / public static ServiceResponse buildFail (int échoue, chaîne msg) {return buildFail (failcode, msg, null); } / ** * Créer un message de réponse échoué * * @param failcode * MSG Body * @return a ServiceResponse qui a échoué Opération * / public static ServiceResponse buildfail (int échoue, string msg, objet body) {return New ServiceResSe StringUtils.isnotblank (msg)? }}Étant donné que nous utilisons la forme d'une classe d'outils statique, nous ne pouvons pas injecter à l'objet TokenMappool (gestionnaire de jetons) via le printemps, puis nous pouvons l'obtenir via l'API fournie par Spring. Ensuite, lors de la construction des informations de réponse, appelez directement la méthode GetToken () de TokenMappool. Cette méthode renverra la chaîne de jeton liée par le thread actuel. Encore une fois, il est important d'appeler Clear manuellement une fois la demande terminée (je l'appelle via l'intercepteur mondial).
L'exemple ci-dessus de la gestion des informations de session backend de l'application qui imite le mécanisme de session de J2EE est tout le contenu que je partage avec vous. J'espère que vous pourrez vous faire référence et j'espère que vous pourrez soutenir Wulin.com plus.