Distributed medical registration system | login interface integrates Alibaba cloud SMS

Requirements completed in this article:
1. Login takes the form of pop-up layer.
2. Login method:
(1) Mobile phone number + mobile phone verification code
(2) Wechat scanning (completed later)
3. There is no registration interface. When logging in for the first time, judge whether the system exists according to the mobile phone number. If it does not exist, it will be registered automatically.
4. If wechat scanning and login is successful, you must bind the mobile phone number, that is, bind the mobile phone number after the first successful scanning, and then log in directly after the subsequent scanning.
5. The gateway judges the login status and how to login. The login layer pops up on the page.

Step 1: build the service user module

1. Start class & configure gateway

Set up the service user module for user login, where:

  • Register the service with Nacos using the @ EnableDiscoveryClient annotation.
  • Use the @ EnableFeignClients(basePackages = "com.gql") annotation to start the remote service call.
    Start swagger scanning with @ ComponentScan(basePackages = "com.gql") annotation.
@SpringBootApplication
@ComponentScan(basePackages = "com.gql")
@EnableDiscoveryClient
@EnableFeignClients(basePackages = "com.gql")
public class ServiceUserApplication {
    public static void main(String[] args) {
        SpringApplication.run(ServiceUserApplication.class, args);
    }
}

Gateway configuration: since the project uses gateway as the gateway, the user module is added now. The gateway configuration needs to be added to the configuration file of gateway module:

# Set routing id
spring.cloud.gateway.routes[2].id=service-user
#Set the uri of the route
spring.cloud.gateway.routes[2].uri=lb://service-user
#Set the route assertion. The agent servicerId is the / auth / path of auth service
spring.cloud.gateway.routes[2].predicates= Path=/*/user/**

2. Three layer call

The login(@RequestBody LoginVo loginVo) method of the Controller layer calls the loginUser(LoginVo loginVo) method of the Service layer, and then calls redisTemplate and baseMapper to operate Redis and MySQL respectively.

Controller layer login(@RequestBody LoginVo loginVo) method:

    @Autowired
    private UserInfoService userInfoService;

    // User mobile number login interface
    @PostMapping("login")
    public Result login(@RequestBody LoginVo loginVo) {
        Map<String, Object> info = userInfoService.loginUser(loginVo);
        return Result.ok(info);
    }

Service layer loginUser(LoginVo loginVo) method:

@Autowired
    private RedisTemplate<String, String> redisTemplate;

    // User mobile number login interface
    @Override
    public Map<String, Object> loginUser(LoginVo loginVo) {
        // Get the entered mobile phone number and verification code from loginVo
        String phone = loginVo.getPhone();
        String code = loginVo.getCode();

        // Judge whether the mobile phone number and verification code are empty
        if (StringUtils.isEmpty(phone) || StringUtils.isEmpty(code)) {
            throw new YyghException(ResultCodeEnum.PARAM_ERROR);
        }

        // Verification code
        String redisCode = redisTemplate.opsForValue().get(phone);
        if (!code.equals(redisCode)) {
            throw new YyghException(ResultCodeEnum.CODE_ERROR);
        }


        // Judge whether it is the first login: query the database according to the mobile phone number
        QueryWrapper<UserInfo> wrapper = new QueryWrapper<>();
        wrapper.eq("phone", phone);
        UserInfo userInfo = baseMapper.selectOne(wrapper);

        // If it is the first time to log in with your mobile phone
        if (userInfo == null) {
            // Add information to database
            userInfo = new UserInfo();
            userInfo.setName("");
            userInfo.setPhone(phone);
            userInfo.setStatus(1);
            baseMapper.insert(userInfo);
        }

        // Check whether it is disabled
        if (userInfo.getStatus() == 0) {
            throw new YyghException(ResultCodeEnum.LOGIN_DISABLED_ERROR);
        }

        // Not the first time, log in directly
        // Return login information
        // Return login user name
        // Return tocken information
        HashMap<String, Object> map = new HashMap<>();
        String name = userInfo.getName();
        // If the user name is empty, get the nickname
        if (StringUtils.isEmpty(name)) {
            name = userInfo.getNickName();
        }
        // If the nickname is still empty, go to get the mobile phone number
        if (StringUtils.isEmpty(name)) {
            name = userInfo.getPhone();
        }
        map.put("name", name);

        // Using JWT to generate a token string
        String token = JwtHelper.createToken(userInfo.getId(), name);
        map.put("tocken", token);
        return map;
    }

Step 2: integrate JWT

JWT (Json Web Token) is an open standard based on JSON, which is implemented to transfer declarations between network application environments. The declaration of JWT is generally used to transfer the authenticated user identity information between identity providers and service providers, so as to obtain resources from the resource server. For example, it is used for user login. The most important function of JWT is the anti-counterfeiting of token information.

A JWT consists of three parts: public part, private part and signature part. The combination of the three is base64 encoded to obtain JWT. Because base64 encoding is not encryption, it just turns the plaintext information into an invisible string. But in fact, base64 can be encoded into plaintext with some tools, so don't put private information in JWT.

Integrate JWT into common util module: the version has been in yygh parent module POM XML add

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
</dependency>

Write the JwtHelper class in the common util module:

public class JwtHelper {
    // Expiration time
    private static long tokenExpiration = 24 * 60 * 60 * 1000;
    // Signature key
    private static String tokenSignKey = "123456";

    // Generate token according to parameters
    public static String createToken(Long userId, String userName) {
        String token = Jwts.builder()
                .setSubject("YYGH-USER")
                .setExpiration(new Date(System.currentTimeMillis() + tokenExpiration))
                .claim("userId", userId)
                .claim("userName", userName)
                .signWith(SignatureAlgorithm.HS512, tokenSignKey)
                .compressWith(CompressionCodecs.GZIP)
                .compact();
        return token;
    }

    // Get the user id according to the token string
    public static Long getUserId(String token) {
        if (StringUtils.isEmpty(token)) {
            return null;
        }
        Jws<Claims> claimsJws = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token);
        Claims claims = claimsJws.getBody();
        Integer userId = (Integer) claims.get("userId");
        return userId.longValue();
    }

    // Get the user's name according to the token string
    public static String getUserName(String token) {
        if (StringUtils.isEmpty(token)) {
            return "";
        }
        Jws<Claims> claimsJws
                = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token);
        Claims claims = claimsJws.getBody();
        return (String) claims.get("userName");
    }

    // test
    public static void main(String[] args) {
        String token = JwtHelper.createToken(1L, "Hudie");
        // token = header information + subject + signature hash
        System.out.println(token);
        System.out.println(JwtHelper.getUserId(token));
        System.out.println(JwtHelper.getUserName(token));
    }
}

Step 3: build a service MSM SMS module (integrate Alibaba cloud SMS)

1. Start class & configure gateway

Build a service MSM module for SMS login, including:

  • Register the service with Nacos using the @ EnableDiscoveryClient annotation.
  • Use @ SpringBootApplication(exclude = DataSourceAutoConfiguration.class) annotation to cancel the automatic configuration of data source, because sending SMS does not need to call MySQL database.
    Start swagger scanning with @ ComponentScan(basePackages = "com.gql") annotation.
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@EnableDiscoveryClient
// swagger scan
@ComponentScan(basePackages = {"com.gql"})
public class ServiceMsmApplication {
    public static void main(String[] args) {
        SpringApplication.run(ServiceMsmApplication.class, args);
    }
}

Gateway configuration: since the project uses gateway as the gateway, the short message module is added now. The gateway configuration needs to be added to the configuration file of gateway module:

# Set routing id
spring.cloud.gateway.routes[3].id=service-msm
#Set the uri of the route
spring.cloud.gateway.routes[3].uri=lb://service-msm
#Set the route assertion. The agent servicerId is the / auth / path of auth service
spring.cloud.gateway.routes[3].predicates= Path=/*/msm/**

2. SMS configuration file & read configuration class

SMS profile: add the regionId, accessKeyId and secret of Alibaba cloud SMS in the properties of the SMS module:

# Alibaba cloud server of Hangzhou node is used here
aliyun.sms.regionId=cn-hangzhou
aliyun.sms.accessKeyId=[secrecy]
aliyun.sms.secret=[secrecy]

Read configuration file class: read the content of the configuration file in the configuration class:

@Component
public class ConstantPropertiesUtils implements InitializingBean {

    @Value("${aliyun.sms.regionId}")
    private String regionId;

    @Value("${aliyun.sms.accessKeyId}")
    private String accessKeyId;

    @Value("${aliyun.sms.secret}")
    private String secret;
    
    public static String REGION_Id;
    public static String ACCESS_KEY_ID;
    public static String SECRECT;

    @Override
    public void afterPropertiesSet() throws Exception {
        REGION_Id = regionId;
        ACCESS_KEY_ID = accessKeyId;
        SECRECT = secret;
    }
}

3. Generate verification code class

This class includes the method of generating 4-digit verification code and 6-digit verification code.

public class RandomUtil {

    private static final Random random = new Random();

    private static final DecimalFormat fourdf = new DecimalFormat("0000");

    private static final DecimalFormat sixdf = new DecimalFormat("000000");

    public static String getFourBitRandom() {
        return fourdf.format(random.nextInt(10000));
    }

    public static String getSixBitRandom() {
        return sixdf.format(random.nextInt(1000000));
    }

    /**
     * Given an array, extract n data
     * @param list
     * @param n
     * @return
     */
    public static ArrayList getRandom(List list, int n) {

        Random random = new Random();

        HashMap<Object, Object> hashMap = new HashMap<Object, Object>();

        // Generate random numbers and store them in HashMap
        for (int i = 0; i < list.size(); i++) {

            int number = random.nextInt(100) + 1;

            hashMap.put(number, i);
        }

        // Importing arrays from HashMap
        Object[] robjs = hashMap.values().toArray();

        ArrayList r = new ArrayList();

        // Traverse the array and print the data
        for (int i = 0; i < n; i++) {
            r.add(list.get((int) robjs[i]));
            System.out.print(list.get((int) robjs[i]) + "\t");
        }
        System.out.print("\n");
        return r;
    }
}

4. Layer 3 call

The sendCode(@PathVariable String phone) method of Controller layer calls redisTemplate to get the generated verification code directly, then calls send(phone, code) method of Service layer to send the mobile phone verification code through Ali cloud.

@RestController
@RequestMapping("/api/msm")
public class MsmApiController {

    @Autowired
    private MsmService msmService;

    @Autowired
    private RedisTemplate<String, String> redisTemplate;


    // Send mobile verification code
    @GetMapping("send/{phone}")
    public Result sendCode(@PathVariable String phone) {
        // Get the mobile phone verification code from redis. If you get it, return ok
        // (key: mobile phone number, value: verification code)
        String code = redisTemplate.opsForValue().get(phone);
        if (!StringUtils.isEmpty(code)) {
            return Result.ok();
        }
        // If it cannot be obtained, a 6-digit verification code is generated
        code = RandomUtil.getSixBitRandom();
        // Secretly print to console
        System.out.println(code);
        // Call service to return and send through Integrated SMS service
        boolean isSend = msmService.send(phone, code);

        // Put the generated verification code into redis and set the effective time
        if (isSend) {
            // The verification code is invalid for more than 1 minute
            redisTemplate.opsForValue().set(phone, code, 1, TimeUnit.MINUTES);
            return Result.ok();
        } else {
            return Result.fail().message("Sending SMS failed");
        }
    }
}

Mobile phone verification code sent by Service layer:

@Service
public class MsmServiceImpl implements MsmService {
    // Send mobile verification code
    @Override
    public boolean send(String phone, String code) {
        // Judge whether the mobile phone number is empty
        if (StringUtils.isEmpty(phone)) {
            return false;
        }

        // Integrate Alibaba cloud SMS services


        // Set relevant parameters
        DefaultProfile profile = DefaultProfile.
                getProfile(ConstantPropertiesUtils.REGION_Id,
                        ConstantPropertiesUtils.ACCESS_KEY_ID,
                        ConstantPropertiesUtils.SECRECT);
        IAcsClient client = new DefaultAcsClient(profile);

        CommonRequest request = new CommonRequest();
        // If it is HTTPS, you need to set ↓
        //request.setProtocol(ProtocolType.HTTPS);
        request.setMethod(MethodType.POST);
        request.setDomain("dysmsapi.aliyuncs.com");
        request.setVersion("2017-05-25");
        request.setAction("SendSms");

        //cell-phone number
        request.putQueryParameter("PhoneNumbers", phone);
        //Signature name
        request.putQueryParameter("SignName", "Kangaroo Jiari");
        //Template code
        request.putQueryParameter("TemplateCode", "SMS_215315088");
        //The verification code uses json format {"code":"123456"}
        Map<String, Object> param = new HashMap();
        param.put("code", code);
        request.putQueryParameter("TemplateParam", JSONObject.toJSONString(param));

        //Call method to send SMS
        try {
            CommonResponse response = client.getCommonResponse(request);
            System.out.println(response.getData());
            return response.getHttpResponse().isSuccess();
        } catch (ServerException e) {
            e.printStackTrace();
        } catch (ClientException e) {
            e.printStackTrace();
        }
        return false;
    }
}

Step 4: Login Page Front

1. Encapsulate api requests

Create API folder, create / API / userinfo js,/api/msm.js

import request from '@/utils/request'

const api_name = `/api/user`

export default {
    login(userInfo) {
        return request({
            url: `${api_name}/login`,
            method: `post`,
            data: userInfo
        })
    }
}
import request from '@/utils/request'

const api_name = `/api/msm`

export default {
    sendCode(mobile) {
        return request({
            url: `${api_name}/send/${mobile}`,
            method: `get`
        })
    }
}

2. Add login component

After successful login, we need to record the user information in the cookie, so execute NPM install JS cookie on the command line of vscode.

The login pop-up component is a common layer, so we put it in the header component and modify layouts / myheader Vue file:

For specific code, click here to view the warehouse.

3. Log in to global events

At present, the login pop-up layer is in the myheader component, and the login button is also in the same component. Click login and call showLogin() method.

On the reservation registration page, when selecting a department to register, we need to judge whether we are currently logged in. If so, we can enter the next page; If there is no login, the login layer needs to be displayed. We can register a global login event. When we need to log in to the layer, we send a login event, the header monitors the login event, and then we trigger the click event of the login button to open the login layer.

Register and listen to login events in the header, and modify myheader Vue components:

①. Introduce vue

import Vue from 'vue'

② Registration and listening events

  // After rendering the page
  mounted() {
    // Register global login event object
    window.loginEvent = new Vue();
    // Listen for login events
    loginEvent.$on("loginDialogEvent", function () {
      document.getElementById("loginDialog").click();
    });
    // Trigger the event and display the login layer: loginevent$ emit('loginDialogEvent')
  },

Adjust the registration page and modify / pages/hospital/_hoscode.vue components:

① Introducing cookie s

import cookie from 'js-cookie'

② Modification method

    schedule(depcode) {
      // Login judgment
      let token = cookie.get("token");
      if (!token) {
        loginEvent.$emit("loginDialogEvent");
        return;
      }
      window.location.href =
        "/hospital/schedule?hoscode=" + this.hoscode + "&depcode=" + depcode;
    },

Additional: user authentication and gateway integration

Idea:

  1. All requests will pass through the service gateway. The service gateway exposes the service to the outside world and carries out unified user authentication at the gateway;
  2. Since we want to authenticate users in the gateway, the gateway needs to know which URLs to authenticate, so we have to make rules for ur.
  3. For asynchronous requests from Api interfaces, we adopt url rule matching, such as: / api//auth /, and all that meet this rule must be authenticated by the user.

Therefore, we need to adjust the server gateway module.

Add a service in the gateway fill1

@Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String path = request.getURI().getPath();
        System.out.println("===" + path);

        //Internal service interface, external access is not allowed
        if (antPathMatcher.match("/**/inner/**", path)) {
            ServerHttpResponse response = exchange.getResponse();
            return out(response, ResultCodeEnum.PERMISSION);
        }

        //api Interface, asynchronous request, verification, user must log in
        if (antPathMatcher.match("/api/**/auth/**", path)) {
            Long userId = this.getUserId(request);
            if (StringUtils.isEmpty(userId)) {
                ServerHttpResponse response = exchange.getResponse();
                return out(response, ResultCodeEnum.LOGIN_AUTH);
            }
        }
        return chain.filter(exchange);
    }

See the warehouse for the website gateway filter code.

2. Adjust the front-end code

When requesting the server-side interface, we take the token by default. If the interface to be logged in does not have a token or the token expires, the server-side will return to 208 status, and then send a login event to open the login pop-up layer for login. Utils / request. Needs to be modified JS file:

import axios from 'axios'
import { MessageBox, Message } from 'element-ui'
import cookie from 'js-cookie'

// Create an axios instance
const service = axios.create({
    baseURL: 'http://localhost:9000',
    timeout: 15000 // Request timeout
})
// http request interceptor
service.interceptors.request.use(
    config => {
        // The token will not be processed first, but will be improved in subsequent use

        // Judge whether there is a token value in the cookie
        if (cookie.get('token')) {
            // Put the token value into the cookie
            config.headers['token'] = cookie.get('token')
        }
        return config
    },
    err => {
        return Promise.reject(err)
    })
// http response interceptor
service.interceptors.response.use(
    response => {
        if (response.data.code === 208) {
            // The login input box pops up
            loginEvent.$emit('loginDialogEvent')
            return
        } else {
            if (response.data.code !== 200) {
                Message({
                    message: response.data.message,
                    type: 'error',
                    duration: 5 * 1000
                })
                return Promise.reject(response.data)
            } else {
                return response.data
            }
        }
    },
    error => {
        return Promise.reject(error.response)
    })
export default service

So far, Alibaba cloud SMS has been integrated into the project.

Keywords: Java Javascript

Added by navinj on Fri, 11 Feb 2022 22:26:46 +0200