big technology
Nacos as a registration center
Register the newly created microservice to Nacos


two steps
Configure the application name, nacos discovery registration ip address, and port number in the configuration file
Use the @EnableDiscoveryClient annotation on the startup class to enable the registration function
Use Redis to store verification code information


Just add the dependent configuration address and port number

Direct injection into the StringRedisTemplate template class is
Use as follows
// Get the value in redis according to the key
String redisCode= redisTemplate.opsForValue().get(key value in redis);
// Store the value in Redis
redisTemplate.opsForValue().set(key value, value value, expiration time, expiration unit (TimeUnit.MINUTES));
Use gitee for social login (OAuth2.0) (highlight 1)
Here, because Weibo developer permission application is too slow, use gitee to realize social login

Steps to social login

(1) The front end calls a third-party application as a social login
This is different from the previous model. In the past, the front end directly sends requests to the backend, and then the backend processes the requests. This is to call the third-party application as a social login (that is, first jump to the login authorization page of gitee), and then the user Log in your own gitee account password to authorize. After the authorization is successful, it will jump to the specified application callback address, and then process the application callback address request on the backend

gitee developer background management link: https://gitee.com/oauth/applications/16285

Call the url address of gitee third-party login: <a href=" https://gitee.com/oauth/authorize?client_id= Client ID of your own application&redirect_uri=Successful callback address of your own application&response_type=code&state=1">
<li> <a href="https://gitee.com/oauth/authorize?client_id=32459f971ce6d89cfb9f70899525455d0653cb804f16b38a304e3447dc97d673&redirect_uri=http://auth.saodaimall.com/callback&response_type=code&state=1"> <img style="width: 50px;height: 18px;margin-top: 35px;" src="/static/login/JD_img/gitee.png"/> </a> </li>
(2) Social service OAuth2Controller to handle the application callback address/callback request
Sub-process: (In fact, there are only three lines of code that need to be configured by yourself (lines 42-44 of gitee of OAuth2Controller), and the others are basically fixed)
1> Encapsulate the AccessTokenDTO object and send it to the code cloud server. If the AccessTokenDTO object is correct, it will return an access_token pass token (in fact, a code will be returned after the user authorizes, and then the code and other information will be encapsulated into an AccessTokenDTO object. Find the code cloud server to obtain an access_token pass token, and finally use the access_token pass token to find the code cloud server to ask the user's public information on gitee)
2> After obtaining the access_token pass token, go to the code cloud server to get the user's public information and convert it into a general gitee social login GiteeUser object
3>Remotely call membership service for social login
package com.saodai.saodaimall.auth.controller; import com.alibaba.fastjson.TypeReference; import com.saodai.common.utils.R; import com.saodai.common.vo.MemberResponseVo; import com.saodai.saodaimall.auth.component.GitheeProvider; import com.saodai.saodaimall.auth.feign.MemberFeignService; import com.saodai.saodaimall.auth.vo.AccessTokenDTO; import com.saodai.saodaimall.auth.vo.GiteeUser; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import javax.servlet.http.HttpSession; import static com.saodai.common.constant.AuthServerConstant.LOGIN_USER; /** * Social third-party authorization login **/ @Slf4j @Controller public class OAuth2Controller { @Autowired private MemberFeignService memberFeignService; @Autowired private AccessTokenDTO accessTokenDTO; @Autowired private GitheeProvider githeeProvider; @GetMapping(value = "/callback") // /callback?code=e867a1f4575d4a6161e3249423a0403898253bc593e4b031a8771739ee6769f5&state=1 public String gitee(@RequestParam(name = "code") String code,@RequestParam(name = "state") String state, HttpSession session) throws Exception { System.out.println(code); //The following three lines of code are the values of your own application, and you can see the corresponding value in the third-party application of gitee accessTokenDTO.setClient_id("32459f971ce6d89cfb9f70899525455d0653cb804f16b38a304e3447dc97d673"); accessTokenDTO.setClient_secret("f3046c911c03cadcded986062708150d4232af3ca6aef0259e5a0198d2c15ba5"); accessTokenDTO.setRedirect_uri("http://auth.saodaimall.com/callback"); accessTokenDTO.setCode(code); accessTokenDTO.setState(state); String accessToken = githeeProvider.getAccessToken(accessTokenDTO); //2. Processing if (!StringUtils.isEmpty(accessToken)) { //Get the access_token and turn it into a general gitee social login object GiteeUser giteeUser = githeeProvider.getGiteeUser(accessToken); //Know which social user //1) If the current user enters the website for the first time, it will be automatically registered (a member information is generated for the current social user, and this social account will correspond to the designated member in the future) //Log in or register as a social user //call remote service R oauthLogin = memberFeignService.oauthLogin(giteeUser); if (oauthLogin.getCode() == 0) { MemberResponseVo memberResponseVo = oauthLogin.getData("data", new TypeReference<MemberResponseVo>() {}); log.info("Successful login: User information:{}",memberResponseVo.toString()); //1. When using the session for the first time, order the browser to save the card number and the JSESSIONID cookie //Which website the browser visits in the future will bring the cookie of this website //TODO 1. The token issued by default. Current domain (solve subdomain session sharing problem) //TODO 2. Use JSON serialization to serialize objects into Redis session.setAttribute(LOGIN_USER,memberResponseVo); //2. Log in successfully and jump back to the home page return "redirect:http://saodaimall.com"; } else { return "redirect:http://auth.saodaimall.com/login.html"; } } else { return "redirect:http://auth.saodaimall.com/login.html"; } } }
package com.saodai.saodaimall.auth.vo; /** * AccessTokenDTO Object encapsulation (gitee social login token) */ import org.springframework.stereotype.Component; @Component public class AccessTokenDTO { private String client_id; private String client_secret; private String code; private String redirect_uri; private String state; public String getClient_id() { return client_id; } public void setClient_id(String client_id) { this.client_id = client_id; } public String getClient_secret() { return client_secret; } public void setClient_secret(String client_secret) { this.client_secret = client_secret; } public String getCode() { return code; } public void setCode(String code) { this.code = code; } public String getRedirect_uri() { return redirect_uri; } public void setRedirect_uri(String redirect_uri) { this.redirect_uri = redirect_uri; } public String getState() { return state; } public void setState(String state) { this.state = state; } }
package com.saodai.saodaimall.auth.component; import com.alibaba.fastjson.JSON; import com.saodai.saodaimall.auth.vo.AccessTokenDTO; import com.saodai.saodaimall.auth.vo.GiteeUser; import okhttp3.*; import org.springframework.stereotype.Component; import java.io.IOException; /** * Request code cloud server */ @Component public class GitheeProvider { //Initiate a post request to obtain AccessToken public String getAccessToken(AccessTokenDTO accessTokenDTO){ MediaType mediaType= MediaType.get("application/json; charset=utf-8"); OkHttpClient client = new OkHttpClient(); RequestBody body = RequestBody.create(mediaType, JSON.toJSONString(accessTokenDTO)); Request request = new Request.Builder() .url("https://gitee.com/oauth/token?grant_type=authorization_code&code="+accessTokenDTO.getCode()+ "&client_id="+accessTokenDTO.getClient_id()+"&redirect_uri="+accessTokenDTO.getRedirect_uri()+ "&client_secret="+accessTokenDTO.getClient_secret()) .post(body) .build(); try (Response response = client.newCall(request).execute()) { String string = response.body().string(); System.out.println(string); String str1 = string.split(":")[1]; String str2 = str1.split("\"")[1]; return str2; } catch (IOException e) { e.printStackTrace(); } return null; } //Initiate a get request to return the GitUser object, public GiteeUser getGiteeUser(String token){ OkHttpClient client = new OkHttpClient(); Request request = new Request.Builder() .url("https://gitee.com/api/v5/user?access_token="+token) .build(); try (Response response = client.newCall(request).execute()) { String string=response.body().string(); GiteeUser giteeUser = JSON.parseObject(string, GiteeUser.class); return giteeUser; } catch (IOException e) { e.printStackTrace(); } return null; } }
package com.saodai.saodaimall.auth.vo; import lombok.Data; /** * GiteeUser Object encapsulation (gitee object for social login) */ @Data public class GiteeUser { //gitee username private String name; //gitee-user-id private String id; //gitee user introduction private String bio; }
(3) Remotely call member services for social login
/** * social login * @param giteeUser * @return * @throws Exception */ @PostMapping(value = "/oauth2/login") public R oauthLogin(@RequestBody GiteeUser giteeUser) throws Exception { MemberEntity memberEntity = memberService.login(giteeUser); if (memberEntity != null) { return R.ok().setData(memberEntity); } else { return R.error(BizCodeEnum.LOGINACCT_PASSWORD_EXCEPTION.getCode(),BizCodeEnum.LOGINACCT_PASSWORD_EXCEPTION.getMessage()); } }
/** * social login * @param giteeUser * @return * @throws Exception */ @Override public MemberEntity login(GiteeUser giteeUser) throws Exception { //Get gitee user unique id String giteeUserId = giteeUser.getId(); //1. Determine whether the current social user has logged into the system MemberEntity memberEntity = this.baseMapper.selectOne(new QueryWrapper<MemberEntity>().eq("social_id", giteeUserId)); //This user is already registered if (memberEntity != null) { return memberEntity; } else { //2. If there is no record corresponding to the current social user, we need to register one MemberEntity register = new MemberEntity(); //Social gitee login id as member id register.setId(Long.valueOf(giteeUserId)); register.setSocialName(giteeUser.getName()); register.setUsername(giteeUser.getName()); register.setNickname(giteeUser.getName()); register.setCreateTime(new Date()); register.setSocialBio(giteeUser.getBio()); register.setSocialId(giteeUserId); //Insert user information into the database this.baseMapper.insert(register); return register; } }
package com.saodai.saodaimall.member.entity; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; import java.io.Serializable; import java.util.Date; /** * member */ @Data @TableName("ums_member") public class MemberEntity implements Serializable { private static final long serialVersionUID = 1L; /** * id */ @TableId private Long id; /** * membership level id */ private Long levelId; /** * username */ private String username; /** * password */ private String password; /** * Nick name */ private String nickname; /** * phone number */ private String mobile; /** * Mail */ private String email; /** * avatar */ private String header; /** * gender */ private Integer gender; /** * Birthday */ private Date birth; /** * city */ private String city; /** * Profession */ private String job; /** * Signature */ private String sign; /** * user source */ private Integer sourceType; /** * integral */ private Integer integration; /** * growth value */ private Integer growth; /** * enabled state */ private Integer status; /** * Registration time */ private Date createTime; /** * ID of the social login user */ private String socialId; /** * The name of the social login user */ private String socialName; /** * Self-introduction for social login users */ private String socialBio; }
Integrate SpringSession to solve the problem of session synchronization and non-sharing (highlight 2)
The purpose of using SpringSession is to solve the problem that distributed session s are not synchronized and not shared. Using SpringSession can store all sessions in redis, which solves the problem of session asynchrony, and then expands the scope, which solves the problem of not sharing sessions. SpringSession does not require explicit operations (that is, it does not need to use the StringRedisTemplate class. The method to put the session into redis, you don’t have to do anything, just put the data into HttpSession normally), because of the integration of SpringSession, the data put into HttpSession will be automatically put into redis, Since serialization is configured, the session will be serialized as a json string and put into redis, and then when a front-end service wants to fetch the session, it will also be automatically fetched from redis


Note: Since the type of springsession used here is redis, both springsession and redis must be added with dependencies and configurations (so the session will be stored in the Redis cache)
(1) Import dependencies
<!-- to integrate springsession to solve distributed session Out of sync, out of share problem--> <dependency> <groupId>org.springframework.session</groupId> <artifactId>spring-session-data-redis</artifactId> </dependency> <!-- to integrate redis--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
(2) Configure springsession in the application.properties configuration file
#configure springsession spring.session.store-type=redis server.servlet.session.timeout=30m #Configure the ip address of redis spring.redis.host=192.168.241.128
(3) Add springSession configuration class in config configuration
package com.saodai.saodaimall.order.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.RedisSerializer; import org.springframework.session.web.http.CookieSerializer; import org.springframework.session.web.http.DefaultCookieSerializer; /** * springSession Configuration class (session configuration of all services to use session should be consistent) */ @Configuration public class GulimallSessionConfig { /** * Configure session (mainly to enlarge the scope of session) * @return */ @Bean public CookieSerializer cookieSerializer() { DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer(); //Zoom in cookieSerializer.setDomainName("saodaimall.com"); cookieSerializer.setCookieName("SAODAISESSION"); return cookieSerializer; } /** * Configure the Session to be stored in redis in the format of json (actually json serialization) * @return */ @Bean public RedisSerializer<Object> springSessionDefaultRedisSerializer() { return new GenericJackson2JsonRedisSerializer(); } }
(4) Add the @EnableRedisHttpSession annotation to the startup class
package com.saodai.saodaimall.order; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession; /** * Order service startup class */ @EnableFeignClients @EnableRedisHttpSession @EnableDiscoveryClient @SpringBootApplication public class SaodaimallOrderApplication { public static void main(String[] args) { SpringApplication.run(SaodaimallOrderApplication.class, args); } }
The principle of SpringSession
The implementation of Spring-Session is to design a filter SessionRepositoryFilter. Whenever a request comes in, the filter will first convert the two objects ServletRequest and ServletResponse into Spring's internal wrapper classes SessionRepositoryRequestWrapper and SessionRepositoryResponseWrapper objects, which use a SessionRepositoryRequestWrapper class to take over Http Session and overridden getSession method to achieve session creation and management. Transfer the process of creating a session by the web server to Spring-Session for creation. The originally created session is stored in the memory of the Web server, and the session information created by Spring-Session can be stored in a third-party service, such as: redis,mysql wait. Web servers share data by connecting to third-party services to achieve Session sharing!
@Order(SessionRepositoryFilter.DEFAULT_ORDER) public class SessionRepositoryFilter<S extends Session> extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { request.setAttribute(SESSION_REPOSITORY_ATTR, this.sessionRepository); //Create a SessionRepositoryRequestWrapper using HttpServletRequest, HttpServletResponse and servletContext SessionRepositoryRequestWrapper wrappedRequest = new SessionRepositoryRequestWrapper( request, response, this.servletContext); SessionRepositoryResponseWrapper wrappedResponse = new SessionRepositoryResponseWrapper( wrappedRequest, response); try { filterChain.doFilter(wrappedRequest, wrappedResponse); } finally { //Save session information wrappedRequest.commitSession(); } } } @Override public HttpSessionWrapper getSession(boolean create) { //Obtain the attributes representing the Session in the current Request scope, and the caching effect avoids obtaining it from the sessionRepository every time HttpSessionWrapper currentSession = getCurrentSession(); if (currentSession != null) { return currentSession; } //Find a cookie called SESSION in the client, get the sessionId, and use the sessionRepository object to search in Redis according to the sessionId S requestedSession = getRequestedSession(); //If the value is queried from redis if (requestedSession != null) { //The sessionId exists on the client and has not expired if (getAttribute(INVALID_SESSION_ID_ATTR) == null) { requestedSession.setLastAccessedTime(Instant.now()); this.requestedSessionIdValid = true; currentSession = new HttpSessionWrapper(requestedSession, getServletContext()); currentSession.setNew(false); //Set the Session to the request attribute setCurrentSession(currentSession); return currentSession; } } else { // This is an invalid session id. No need to ask again if // request.getSession is invoked for the duration of this request if (SESSION_LOGGER.isDebugEnabled()) { SESSION_LOGGER.debug( "No session found by id: Caching result for getSession(false) for this HttpServletRequest."); } setAttribute(INVALID_SESSION_ID_ATTR, "true"); } //Return null directly without creating a Session if (!create) { return null; } if (SESSION_LOGGER.isDebugEnabled()) { SESSION_LOGGER.debug( "A new session was created. To help you troubleshoot where the session was created we provided a StackTrace (this is not an error). You can prevent this from appearing by disabling DEBUG logging for " + SESSION_LOGGER_NAME, new RuntimeException( "For debugging purposes only (not an error)")); } //Execution to this point indicates that a new Session needs to be created // Create the RedisSession object through sessionRepository S session = SessionRepositoryFilter.this.sessionRepository.createSession(); session.setLastAccessedTime(Instant.now()); currentSession = new HttpSessionWrapper(session, getServletContext()); setCurrentSession(currentSession); return currentSession; } // Create the RedisSession object through sessionRepository @Override public RedisSession createSession() { Duration maxInactiveInterval = Duration .ofSeconds((this.defaultMaxInactiveInterval != null) ? this.defaultMaxInactiveInterval : MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS); RedisSession session = new RedisSession(maxInactiveInterval); session.flushImmediateIfNecessary(); return session; }
Good article reference: Spring-Session realizes the principle and analysis of session sharing
small technology
Use view mapper to realize page jump

The traditional way of writing is to implement in the controller

Customize a configuration class to implement the WebMvcConfigurer interface, and then override the addViewControllers method to add the view mapper
Use configuration files to dynamically configure property values


In this way, the attribute value can be changed by modifying the corresponding value in the configuration file. The core annotation is @ConfigurationProperties (prefix=""), and the @Data annotation should also be added
Use Feign to call services remotely

Add the openFeign dependency and start the remote caller via the @EnableFeignClients annotation to use the feign remote call service


Define a remote call interface, use the @FeignClient annotation to specify which service to call, just take the method signature of the third-party service controller, pay attention to the path must be written correctly, especially if there is a parent path, don’t forget to write

Inject the newly written remote interface into the server that needs to be called remotely, and then call it. For example, here is the interface where the authentication center calls the third-party service to generate the verification code.
Use the exception mechanism
/** * Sign Up */ @Override public void register(MemberUserRegisterVo vo) { MemberEntity memberEntity = new MemberEntity(); //set default level MemberLevelEntity levelEntity = memberLevelDao.getDefaultLevel(); memberEntity.setLevelId(levelEntity.getId()); //Set other default information //Check if username and phone number are unique. Perception exception, exception mechanism (the exception mechanism is to throw a specific exception if there is a problem, and continue to execute the following statement if there is no problem) checkPhoneUnique(vo.getPhone()); checkUserNameUnique(vo.getUserName()); memberEntity.setNickname(vo.getUserName()); memberEntity.setUsername(vo.getUserName()); //The password is encrypted with MD5 salt value (the result of each encryption of the same data encrypted by salt value is different, and the password is verified by the match method) // (Note that md5 cannot be used to encrypt the database directly, because the rainbow table can crack md5, the so-called rainbow table is to reversely exit md5 through a large amount of md5 data // Note that MD5 is irreversible, but it can be cracked violently through the rainbow table) BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder(); String encode = bCryptPasswordEncoder.encode(vo.getPassword()); memberEntity.setPassword(encode); memberEntity.setMobile(vo.getPhone()); memberEntity.setGender(0); memberEntity.setCreateTime(new Date()); //save data this.baseMapper.insert(memberEntity); } /** * An abnormal mechanism method to check whether the mobile phone number is duplicated * @param phone * @throws PhoneException */ @Override public void checkPhoneUnique(String phone) throws PhoneException { Long phoneCount = this.baseMapper.selectCount(new QueryWrapper<MemberEntity>().eq("mobile", phone)); //usernameCount > 0 means the phone number already exists if (phoneCount > 0) { throw new PhoneException(); } } /** * Exception mechanism method to check whether username is duplicate * @param userName * @throws UsernameException */ @Override public void checkUserNameUnique(String userName) throws UsernameException { Long usernameCount = this.baseMapper.selectCount(new QueryWrapper<MemberEntity>().eq("username", userName)); //usernameCount > 0 means the username already exists if (usernameCount > 0) { throw new UsernameException(); } }
The specific implementation of the exception mechanism method defined above to check the user name and phone number
/** * Member registration function * @param vo * @return */ @PostMapping(value = "/register") public R register(@RequestBody MemberUserRegisterVo vo) { try { memberService.register(vo); } catch (PhoneException e) { //BizCodeEnum.PHONE_EXIST_EXCEPTION=The same phone number exists 15002 return R.error(BizCodeEnum.PHONE_EXIST_EXCEPTION.getCode(),BizCodeEnum.PHONE_EXIST_EXCEPTION.getMessage()); } catch (UsernameException e) { //BizCodeEnum.USER_EXIST_EXCEPTION=Product inventory is insufficient 21000 return R.error(BizCodeEnum.USER_EXIST_EXCEPTION.getCode(),BizCodeEnum.USER_EXIST_EXCEPTION.getMessage()); } return R.ok(); }
The reason for using the exception mechanism is to hope that the controller can detect and handle exceptions
package com.saodai.saodaimall.member.exception; public class UsernameException extends RuntimeException { public UsernameException() { super("same username exists"); } } package com.saodai.saodaimall.member.exception; public class PhoneException extends RuntimeException { public PhoneException() { super("same phone number exists"); } }
Extract the above two individual exceptions and encapsulate them into exception classes
Encrypt with MD5 salt value
encryption

First create an encryptor BCryptPasswordEncoder, then call its encode method to put the password to be encrypted, and a string of encrypted values will be automatically generated

Note that the generated value of the same password is different each time
decrypt

After getting the encrypted data from the database, call the matches method to match whether the two passwords are consistent. If they are consistent, return true, and if they are inconsistent, return false. The previous password is the old password (unencrypted password), and the latter passwordDb is the database. encrypted password