2022 authorized login of the latest wechat applet (front and back end separation)

1, Foreword

First, let's talk about two major changes in wechat applet recently:

1. The interface for obtaining user information is from the original Wx Replace getUserInfo with Wx getUserProfile

For the new version of applet released after 24:00 on April 28, 2021, the developer calls Wx GetUserInfo will no longer pop up a pop-up window and directly return anonymous user personal information. The ability to obtain encrypted openID and unionID data will not be adjusted.

Add getUserProfile interface. If developers need to obtain the user's personal information, they can use Wx getUserProfile interface, which only returns the user's personal information and does not contain the user's identity identifier. The desc attribute in this interface (stating the purpose after obtaining the user's personal information) will be displayed in the pop-up window later. Please fill in it carefully. Every time the developer obtains the user's personal information through this interface, it needs the user's confirmation. Please keep the avatar nickname quickly filled in by the user properly to avoid repeated pop ups.

The most intuitive feeling of the new version of the applet: when entering the applet, it will not pop up immediately, but when the user performs relevant operations, such as clicking a request button, it will pop up to prompt authorization.

Official announcement: Instructions for interface adjustment related to applet login and user information | wechat open community

2. The applet obtains the interface related to user information and no longer returns user gender and region information

According to relevant laws and regulations, further standardize developers to call the user information related interface, and the applet obtains the user information related interface without returning the user's gender and region information. This means that the open interface can only obtain the user's Avatar and nickname, and the rest of the information needs to be filled in by the user.

For developers, this change reduces the difficulty of obtaining information, but relatively, the importance of the obtained data has also decreased. In the past, the way to obtain information required the applet to obtain encryData and iv to the back-end for decryption, and the back-end returned the relevant information to the front-end. Now you can directly obtain the avatar and user name, just call the back-end interface to store it in the database.

Official announcement: Wechat public platform user information related interface adjustment announcement | wechat open community

2, Pre preparation

1. Technology stack

Front end: wechat applet development (without cloud development)

Back end: spring boot + mysql + mybatis + jwt

2. Understand the login process

General process:

1. The front end calls Wx Login gets the code, and then calls the back-end interface to pass the code

Note: code is temporary. It only takes 5 minutes to use and can only be used once

2. Get the unique ID of the back-end user with open code and session_key (can be used to decrypt private information, encrydata, now only get avatar and nickname), associated with openid and session_key customize the login session and use the session to generate a token

Note: the resolved openid and session cannot be_ If the key is returned directly to the front end, it will cause information security problems

3. Return the token to the front end

4. Front end cache token

5. When the user logs in, the login interface obtains the token, and then calls other interfaces, the interceptor intercepts it. If the token is valid, the release request will be made; If the token fails (for reasons such as nonexistence, expiration, incorrect format, etc.), the interface cannot be accessed and you need to log in again.

Note: if you think the token verification is too complicated, you can take the second place and use the Wx provided by wechat applet Checkseesion check the issued session_ Whether the key expires (fixed to two days).

wx.checkSeesion is a front-end inspection, which is very convenient, but its disadvantages are also obvious: it takes a long time and usually takes 300+ms. in addition, when transmitting private data at the front and back ends, additional consideration needs to be given to data security (taking openid as an example, when the front-end needs to pass openid, it needs to obtain temporary code first, and then pass it to the back-end, and the back-end uses code to exchange openid, which is very expensive), Therefore, Wx is not recommended for formal development Checkseesion and token verification can better solve the above problems.

3, Development code

1. Back end code

1. config package (mainly some configuration information)

1.InterceptorConfig class (interceptor configuration class)

One thing to note here is that if the interceptor is loaded before the springcontext, null will be automatically injected into the interceptor, so @ Bean needs to be loaded in advance;

In addition, addPathPatterns is used to add intercepted paths. Theoretically, all interfaces need to be intercepted except the login and logout interfaces.

Why use interceptors? After the front end obtains the token, if the token is added to the request body for each request, the front and back end code will be very lengthy. Therefore, the token can be placed in the request header. Each request is intercepted by interceptor, and the developer only needs to pay attention to the business logic information.

@Configuration
public class InterceptorConfig implements WebMvcConfigurer {

    @Bean
    public JwtInterceptor getJwtInterceptor(){
        return new JwtInterceptor();
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry){
        registry.addInterceptor(getJwtInterceptor())
                .addPathPatterns("/user/**") //Intercept user interface
                .excludePathPatterns("/user/index/**");//The login interface is not blocked
    }

}

2. common (public package)

It is different from util package. Util package generally places static tool classes. When there are many tool classes, common package should be used for refinement

1. Result class (used to return messages, simplified version, with more than two actual status codes)

@Data
@NoArgsConstructor
public class Result {
    private int code;
    private String msg;
    private Object data;

    public static Result succ(Object data){
        return succ(200,"Operation successful",data);
    }

    public static Result succ(int code, String msg, Object data) {
        Result r = new Result();
        r.setCode(code);
        r.setData(data);
        r.setMsg(msg);
        return r;
    }

    public static Result succ(String msg, Object data) {
        Result r = new Result();
        r.setCode(200);
        r.setData(data);
        r.setMsg(msg);
        return r;
    }

    public static Result fail(String msg){
        return fail(500,msg,null);
    }

    public static Result fail(int code, String msg, Object data) {
        Result r = new Result();
        r.setCode(code);
        r.setData(data);
        r.setMsg(msg);
        return r;
    }

    public static Result fail(String msg, Object data) {
        Result r = new Result();
        r.setCode(500);
        r.setData(data);
        r.setMsg(msg);
        return r;
    }

    public static Result fail(int code, String msg) {
        Result r = new Result();
        r.setCode(code);
        r.setMsg(msg);
        r.setData(null);
        return r;
    }

}

2. TokenException class (custom exception)

Inherits the runtimeException exception class. RuntimeException is a non checked exception, which is captured only at runtime and will not be checked at compile time. Therefore, it can be thrown directly without a try catch statement (see the interceptor class JwtInterceptor below).

public class TokenException extends RuntimeException{
    public TokenException() {super();}

    public TokenException(String msg) {
        super(msg);
    }
}

3.GlobalExceptionHandler class (Global exception handling)

The token failure status code can be agreed with the front end. Generally, 401 is used to indicate unauthorized

@RestControllerAdvice
public class GlobalExceptionHandler {

    //token failure exception
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(value = TokenException.class)
    public Result handler(TokenException e){
        return Result.fail(401, e.getMessage());
    }

}

3. util package (business Toolkit)

1. JwtUtil class

Import dependency first (using jjwt)

        <!--jwt-->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>

Basic configuration information (write application.yml, secret as password, expire as expiration time, and header as request header name)

markerhub:
  jwt:
    secret: 2019scaumis25710000de581c0f9eb5
    expire: 604800
    header: Authorization

Write Jwt tool class

@Data
@Component
@ConfigurationProperties(prefix = "markerhub.jwt")
public class JwtUtil {

    private String secret;
    private long expire;
    private String header;

    /**
     * Generate jwt token
     * @param session
     * @return
     */
    public String getToken(String session){
        Date nowDate = new Date();
        //Expiration time
        Date expireDate = new Date(nowDate.getTime() + expire * 1000);

        return Jwts.builder()
                .setHeaderParam("typ","JWT")
                .setSubject(session)
                .setIssuedAt(nowDate)
                .setExpiration(expireDate)
                .signWith(SignatureAlgorithm.HS512,secret)
                .compact();
    }


    /**
     * Get the user-defined login session from the token and decrypt it to get the openid
     * @param token
     * @return
     */
    public String getOpenidFromToken(String token){
        String openid;
        String session;
        try{
            //Parsing token to get session
            Claims cliams = getCliamByToken(token);
            session = cliams.getSubject();
            //Decrypt session
            EncryptUtil encryptUtil = new EncryptUtil();
            String jsonString = encryptUtil.decrypt(session);
            JSONObject jsonObject = JSONObject.fromObject(jsonString);
            openid = jsonObject.getString("openid");
            return openid;
        }
        catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }

    /**
     * Get load from token
     * @param token
     * @return
     */
    public Claims getCliamByToken(String token){
        try{
            return Jwts.parser()
                    .setSigningKey(secret)
                    .parseClaimsJws(token)
                    .getBody();
        }
        catch (Exception e){
            return null;
        }
    }

    /**
     * Verification token
     * @param token
     * @return
     */
    public void verifyToken(String token){
        //Throw an exception in the interceptor
        Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
    }

}

2. EncryptUtil class (encryption and decryption tool class)

DES encryption strategy is adopted here, but it is not recommended. AES or RSA can be considered

public class EncryptUtil {
    // String default key value
    private static String strDefaultKey = "2022@#$%^&";

    //encryption tool
    private Cipher encryptCipher = null;

    // Decryption tool
    private Cipher decryptCipher = null;

    /**
     * Default construction method, using default key
     */
    public EncryptUtil() throws Exception {
        this(strDefaultKey);
    }

    /**
     * Specify key construction method
     */

    public EncryptUtil(String strKey) throws Exception {

        Key key = getKey(strKey.getBytes());
        encryptCipher = Cipher.getInstance("DES");
        encryptCipher.init(Cipher.ENCRYPT_MODE, key);
        decryptCipher = Cipher.getInstance("DES");
        decryptCipher.init(Cipher.DECRYPT_MODE, key);
    }

    /**
     * Convert byte array to string representing hexadecimal value, such as: byte[]{8,18} to: 0813, and public static byte []
     */
    public static String byteArr2HexStr(byte[] arrB) throws Exception {
        int iLen = arrB.length;
        // Each byte can only be represented by 2 characters, so the length of the string is twice the length of the array
        StringBuffer sb = new StringBuffer(iLen * 2);
        for (int i = 0; i < iLen; i++) {
            int intTmp = arrB[i];
            // Convert negative numbers to positive numbers
            while (intTmp < 0) {
                intTmp = intTmp + 256;
            }
            // Numbers less than 0F need to be preceded by 0
            if (intTmp < 16) {
                sb.append("0");
            }
            sb.append(Integer.toString(intTmp, 16));
        }
        return sb.toString();
    }

    /**
     * Convert the string representing hexadecimal value into byte array and public static String byteArr2HexStr(byte[] arrB)
     */
    public static byte[] hexStr2ByteArr(String strIn) throws Exception {
        byte[] arrB = strIn.getBytes();
        int iLen = arrB.length;
        // Two characters represent a byte, so the byte array length is the string length divided by 2
        byte[] arrOut = new byte[iLen / 2];
        for (int i = 0; i < iLen; i = i + 2) {
            String strTmp = new String(arrB, i, 2);
            arrOut[i / 2] = (byte) Integer.parseInt(strTmp, 16);
        }
        return arrOut;
    }

    /**
     * Encrypted byte array
     */
    public byte[] encrypt(byte[] arrB) throws Exception {
        return encryptCipher.doFinal(arrB);
    }

    /**
     * Encrypted string
     */
    public String encrypt(String strIn) throws Exception {
        return byteArr2HexStr(encrypt(strIn.getBytes()));
    }

    /**
     * Decrypt byte array
     */
    public byte[] decrypt(byte[] arrB) throws Exception {
        return decryptCipher.doFinal(arrB);
    }

    /**
     * Decrypt string
     */
    public String decrypt(String strIn) throws Exception {
        return new String(decrypt(hexStr2ByteArr(strIn)));
    }

    /**
     * The key is generated from the specified string. The length of the byte array required for the key is 8 bits. If it is less than 8 bits, it is supplemented with 0. If it exceeds 8 bits, only the first 8 bits are taken
     */
    private Key getKey(byte[] arrBTmp) throws Exception {
        // Create an empty 8-bit byte array (default is 0)
        byte[] arrB = new byte[8];
        // Converts the original byte array to 8 bits
        for (int i = 0; i < arrBTmp.length && i < arrB.length; i++) {
            arrB[i] = arrBTmp[i];
        }
        // Generate key
        Key key = new javax.crypto.spec.SecretKeySpec(arrB, "DES");
        return key;
    }
}

4. HttpClientUtil (Http request tool class)

This tool class can be directly cv. It is mainly used to send URL requests to the open interface of wechat applet

public class HttpClientUtil {
    public static String doGet(String url, Map<String, String> param) {

        // Create Httpclient object
        CloseableHttpClient httpclient = HttpClients.createDefault();

        String resultString = "";
        CloseableHttpResponse response = null;
        try {
            // Create uri
            URIBuilder builder = new URIBuilder(url);
            if (param != null) {
                for (String key : param.keySet()) {
                    builder.addParameter(key, param.get(key));
                }
            }
            URI uri = builder.build();

            // Create http GET request
            HttpGet httpGet = new HttpGet(uri);

            // Execute request
            response = httpclient.execute(httpGet);
            // Judge whether the return status is 200
            if (response.getStatusLine().getStatusCode() == 200) {
                resultString = EntityUtils.toString(response.getEntity(), "UTF-8");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (response != null) {
                    response.close();
                }
                httpclient.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return resultString;
    }

    public static String doGet(String url) {
        return doGet(url, null);
    }

    public static String doPost(String url, Map<String, String> param) {
        // Create Httpclient object
        CloseableHttpClient httpClient = HttpClients.createDefault();
        CloseableHttpResponse response = null;
        String resultString = "";
        try {
            // Create Http Post request
            HttpPost httpPost = new HttpPost(url);
            // Create parameter list
            if (param != null) {
                List<NameValuePair> paramList = new ArrayList<>();
                for (String key : param.keySet()) {
                    paramList.add(new BasicNameValuePair(key, param.get(key)));
                }
                // Simulation form
                UrlEncodedFormEntity entity = new UrlEncodedFormEntity(paramList);
                httpPost.setEntity(entity);
            }
            // Execute http request
            response = httpClient.execute(httpPost);
            resultString = EntityUtils.toString(response.getEntity(), "utf-8");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                response.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        return resultString;
    }

    public static String doPost(String url) {
        return doPost(url, null);
    }

    public static String doPostJson(String url, String json) {
        // Create Httpclient object
        CloseableHttpClient httpClient = HttpClients.createDefault();
        CloseableHttpResponse response = null;
        String resultString = "";
        try {
            // Create Http Post request
            HttpPost httpPost = new HttpPost(url);
            // Create request content
            StringEntity entity = new StringEntity(json, ContentType.APPLICATION_JSON);
            httpPost.setEntity(entity);
            // Execute http request
            response = httpClient.execute(httpPost);
            resultString = EntityUtils.toString(response.getEntity(), "utf-8");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                response.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        return resultString;
    }

    /**
     * Sends a request for the POST method to the specified URL
     */
    public static String sendPost(String url, String paramUrl) {
        PrintWriter out = null;
        BufferedReader in = null;
        String result = "";
        try {
            JSONObject param = new JSONObject(paramUrl);
            URL realUrl = new URL(url);
            // Open connection between and URL
            URLConnection conn = realUrl.openConnection();
            // Set common request properties
            conn.setRequestProperty("accept", "*/*");
            conn.setRequestProperty("connection", "Keep-Alive");
            conn.setRequestProperty("user-agent","Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
            // To send a POST request, you must set the following two lines
            conn.setDoOutput(true);
            conn.setDoInput(true);
            // Gets the output stream corresponding to the URLConnection object
            out = new PrintWriter(conn.getOutputStream());
            // Send request parameters
            out.print(param);
            // Buffer of flush output stream
            out.flush();
            // Define BufferedReader input stream to read the response of URL
            in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
            String line;
            while ((line = in.readLine()) != null) {
                result += line;
            }
        } catch (Exception e) {
            System.out.println("send out POST Exception in request!" + e);
            e.printStackTrace();
        }
        // Use the finally block to close the output stream and input stream
        finally {
            try {
                if (out != null) {
                    out.close();
                }
                if (in != null) {
                    in.close();
                }
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
        return result;
    }

}

5. GetUserInfoUtil (get user information tool class)

WX_LOGIN_APPID and WX_LOGIN_SECRET is the account number (appid) and password of wechat applet. The front and back ends must be consistent, otherwise the code cannot be parsed.

public class GetUserInfoUtil {
    // Requested URL
    public static final String WX_LOGIN_URL = "https://api.weixin.qq.com/sns/jscode2session";
    // appid
    public static final String WX_LOGIN_APPID = "";    //Own appid
    // key 
    public static final String WX_LOGIN_SECRET = "";   //Own secret
    // Fixed parameters
    public static final String WX_LOGIN_GRANT_TYPE = "authorization_code";

    //Exchange code for information obtained from the official website of wechat applet
    public static JSONObject getResultJson(String code){
        //Configure request parameters
        Map<String,String> params = new HashMap<>();
        params.put("appid", WX_LOGIN_APPID);
        params.put("secret",WX_LOGIN_SECRET);
        params.put("js_code",code);
        params.put("grant_type",WX_LOGIN_GRANT_TYPE);

        //Send request to wechat server
        String wxRequestResult = HttpClientUtil.doGet(WX_LOGIN_URL,params);
        JSONObject resultJson = JSONObject.fromObject(wxRequestResult);

        return resultJson;
    }

    //Get openid
    public static String getOpenid(String code){
        return getResultJson(code).getString("openid");
    }

}

4. interceptor package

Interceptor class (Jwt. Interceptor class)

The exception thrown here can also be written in the JwtUilt tool class

@Component
public class JwtInterceptor implements HandlerInterceptor {

    @Autowired
    JwtUtil jwtUtil;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler){

        //Get request header token
        String token = request.getHeader("Authorization");

        try{
            jwtUtil.verifyToken(token); //Verification token
            return true; //Release request
        }catch (ExpiredJwtException e){
            e.printStackTrace();
            throw new TokenException("token be overdue!");
        }catch (MalformedJwtException e){
            e.printStackTrace();
            throw new TokenException("token Format error!");
        }catch (SignatureException e){
            e.printStackTrace();
            throw new TokenException("Invalid signature!");
        }catch (IllegalArgumentException e){
            e.printStackTrace();
            throw new TokenException("Illegal request!");
        }catch (Exception e){
            e.printStackTrace();
            throw new TokenException("token Invalid!");
        }
    }

}

5. entity package

Note: first create the corresponding table in the database

1. Owner class

It should be a User class, because bloggers consider pet owners when writing, so they use Owner, which can modify User entities according to their business needs

@Data
@AllArgsConstructor
@NoArgsConstructor
@ApiModel("User entity class")
public class Owner {

    @ApiModelProperty("openid")
    private String openid;

    @ApiModelProperty("User nickname")
    private String nickname;

    @ApiModelProperty("Avatar address")
    private String avatarUrl;

    @ApiModelProperty("User gender")
    private String gender;

    @ApiModelProperty("province")
    private String province;

    @ApiModelProperty("city")
    private String city;

    @ApiModelProperty("area")
    private String district;

    @ApiModelProperty("cell-phone number")
    private String phone;

    @ApiModelProperty("User real name")
    private String name;

    @ApiModelProperty("ID number")
    private String sfznum;

    @ApiModelProperty("User address")
    private String address;

    @Override
    public String toString(){
        return "{" + nickname + "," + avatarUrl + "," + gender + "," + province + "," + city + "," +
                phone + "," + name + "," + sfznum + "," + address + "}";
    }
}

2. OwnerVo class (used to update the information filled in by the user)

Vo class is used to transfer the required data from front to back, because not all fields of database entities are used in practical applications

@Data
@AllArgsConstructor
@NoArgsConstructor
@ApiModel("User personal information Vo class")
public class OwnerVo {

    @ApiModelProperty("cell-phone number")
    private String phone;

    @ApiModelProperty("User real name")
    private String name;

    @ApiModelProperty("Detailed address")
    private String address;

}

6. mapper package (database access package, some people like to use Dao)

1. OwnerMapper interface (it can also be written according to its own business logic)

@Mapper
@Repository
public interface OwnerMapper {

    //New user
    int insertOwner(String openid);

    //Update the information obtained by wechat applet when logging in
    int updateOwnerWxInfo(String openid, String nickname, String avatarUrl);

    //Subsequent users update information after writing personal information
    int updateOwnerInfo(@Param("openid") String openid,
                        @Param("ownerVo") OwnerVo ownerVo);

    //Query user personal information
    Owner queryOwnerInfo(String openid);
}

OwnerMapper.xml (namespace should write its own path)

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.petsafety.mapper.user.OwnerMapper">

    <insert id="insertOwner" parameterType="String">
        insert into owner (openid)
        values (#{openid})
    </insert>

    <update id="updateOwnerWxInfo">
        update owner
        set nickname = #{nickname}, avatar_url = #{avatarUrl}
        where openid = #{openid}
    </update>

    <update id="updateOwnerInfo" >
        update owner
        set phone = #{ownerVo.phone}, name = #{ownerVo.name}, address = #{ownerVo.address}
        where openid = #{openid}
    </update>

    <select id="queryOwnerInfo" parameterType="String" resultType="Owner">
        select  owner.openid ,owner.nickname, owner.avatar_url avatarUrl, owner.gender,
                owner.province, owner.city, owner.phone, owner.name,
                owner.sfznum, owner.address
        from owner
        where openid = #{openid}
    </select>
</mapper>

7. service package (business logic package)

1. OwnerService interface

public interface OwnerService {

    //New user
    int insertOwner(String openid);

    //Insert the information obtained by wechat applet when logging in
    int updateOwnerWxInfo(String openid, String nickname, String avatarUrl);

    //Subsequent users update information after writing personal information
    int updateOwnerInfo(String openid, OwnerVo ownerVo);

    //View user personal information
    Owner queryOwnerInfo(String openid);

}

2.OwnerServiceImpl class

@Service("ownerService")
public class OwnerServiceImpl implements OwnerService{
    @Autowired
    OwnerMapper ownerMapper;

    //New user
    @Override
    public int insertOwner(String openid){return ownerMapper.insertOwner(openid);}

    //Insert the information obtained by wechat applet when logging in
    @Override
    public int updateOwnerWxInfo(String openid, String nickname, String avatarUrl){
        return ownerMapper.updateOwnerWxInfo(openid, nickname, avatarUrl);
    }

    //Update information when subsequent users write personal information
    @Override
    public int updateOwnerInfo(String openid, OwnerVo ownerVo){
        return ownerMapper.updateOwnerInfo(openid, ownerVo);
    }

    //View user personal information
    @Override
    public Owner queryOwnerInfo(String openid){ return ownerMapper.queryOwnerInfo(openid);}

}

8. controller package

1. WxLoginController class

@Api(tags = "WxLoginController")
@RestController
@RequestMapping("/user")
public class WxLoginController {

    @Autowired
    private OwnerService ownerService;

    @Autowired
    JwtUtil jwtUtil;

    @ApiOperation("Wechat authorized login")
    @PostMapping("/index/login")
    public Result authorizeLogin(@NotBlank @RequestParam("code") String code) {

        //Exchange information through code
        JSONObject resultJson = GetUserInfoUtil.getResultJson(code);

        if (resultJson.has("openid")){
            //Get sessionKey and openId
            String sessionKey = resultJson.get("session_key").toString();
            String openid = resultJson.get("openid").toString();

            //Generate custom login session
            String session = null;
            Map<String,String> sessionMap = new HashMap<>();

            sessionMap.put("sessionKey",sessionKey);
            sessionMap.put("openid",openid);
            session = JSONObject.fromObject(sessionMap).toString();

            //Encrypted session
            try {
                EncryptUtil encryptUtil = new EncryptUtil();
                session = encryptUtil.encrypt(session);
            } catch (Exception e) {
                e.printStackTrace();
            }

            //Generate token
            String token = jwtUtil.getToken(session);
            Map<String,String> result = new HashMap<>();
            result.put("token", token);

            //Query whether the user exists
            Owner owner = ownerService.queryOwnerInfo(openid);
            if (owner != null){
                return Result.succ("Login successful", result); //User exists, return result
            }else { //User does not exist, new user information
                int rs = ownerService.insertOwner(openid);
                if (rs <= 0){
                    return Result.fail("Login failed");
                }
                return Result.succ("Login successful", result);
            }
        }

        return Result.fail("privilege grant failed"+ resultJson.getString("errmsg"));
    }

    @ApiOperation("Store user personal information")
    @PostMapping("/index/person-info")
    public Result insertPersonInfo(@RequestParam("nickname") String nickname,
                                   @RequestParam("avatarUrl") String avatarUrl,
                                   HttpServletRequest request){
        
        //Get request header token
        String token = request.getHeader("Authorization");
        //Get openid from token
        String openid = jwtUtil.getOpenidFromToken(token);

        int result = ownerService.updateOwnerWxInfo(openid, nickname, avatarUrl);
        if(result <= 0){
            return Result.fail("Update failed!");
        }

        return Result.succ("Update succeeded!", null);
    }

}

2.OwnerController class

@Api(tags = "OwnerController")
@RestController
@RequestMapping("/user/person-info")
public class OwnerController {

    @Autowired
    OwnerServiceImpl ownerService;

    @Autowired
    JwtUtil jwtUtil;

    @PutMapping("/update-person-info")
    @ApiOperation("Modify user personal information")
    public Result updateOwnerInfo(HttpServletRequest request,
                                  @RequestBody OwnerVo ownerVo){

        //Get request header token
        String token = request.getHeader("Authorization");
        //Get openid
        String openid = jwtUtil.getOpenidFromToken(token);

        int result = ownerService.updateOwnerInfo(openid, ownerVo);

        if (result <= 0){
            return Result.fail("Modification failed",null);
        }

        return Result.succ("Modified successfully",null);
    }

}

2. Front end code

Note: This article focuses on login logic and interface design. Readers should write it by themselves

1,app.js (applet initialization)

App({

  /**
   * When the applet initialization is completed, onLaunch will be triggered (only triggered once globally)
   */
  onLaunch: function () {

    // Silent login
    wx.login({
      success(res) {
        wx.request({ // Call the login interface to obtain the user login credential token
          url: 'http://localhost:8888/user/index/login',
          method: 'POST',
          header: {
            'Content-Type': "application/x-www-form-urlencoded",
          },
          data: {
            code: res.code,
          },
          success(res) { // The interface call is successful. Get the token and cache it
            console.log(res);
            wx.setStorageSync('token', res.data.data.token); // Cache the token into the 'token' field
          },
          fail() {
            console.log("Login error!");
          }
        })
      }
    });

  },
})

2. pages/mine (login interface)

mine.js

const app = getApp()
Page({

  /**
   * Initial data of the page
   */
  data: {
    imgSrc: 'http://scau-pet.oss-cn-guangzhou.aliyuncs.com/picture/2021/09/22/af8d477e21f32347.png ', / / initial picture
    imgSrc2: wx.getStorageSync('avatarUrl'), //Display user's Avatar after authorization
    username: 'Please login>', //Initial text
    username2: wx.getStorageSync('nickName'), //Display user name after authorization
  },

  login() {
    let that = this;
    wx.getUserProfile({
      desc: 'Used to improve user data', //Declare the purpose of obtaining user information
      success(res) {
        console.log(res);
        that.setData({
          imgSrc: res.userInfo.avatarUrl,
          username: res.userInfo.nickName,
        });
        wx.setStorageSync('avatarUrl', res.userInfo.avatarUrl);
        wx.setStorageSync('nickname', res.userInfo.nickName);
        wx.showLoading({
          title: 'Logging in...',
        })

        wx.request({
          url: 'http://localhost:8888/user/index/person-info',
          method: 'POST',
          header: {
            'Authorization': wx.getStorageSync('token'),
            'Content-Type': "application/x-www-form-urlencoded",
          },
          data: {
            nickname: wx.getStorageSync('nickname'),
            avatarUrl: wx.getStorageSync('avatarUrl'),
          },
          success(res) {
            console.log(res);
            wx.hideLoading();
          },
        })
      },
      fail() {
            wx.showModal({
            title: 'Warning notice',
            content: 'You clicked deny authorization,Personal information will not be displayed normally,Click OK to obtain authorization again.',
          });
      }
    })
  },

})

3. pages/index

Different from the traditional account password login, the user must carry a token after silent login. Therefore, whether the user has logged in cannot be judged by whether there is a token. It can be judged by whether there is a user's Avatar nickname in the cache

index.js

var app = getApp();

Page({

  // Jump to login screen
  gotoMine() {
    wx.showModal({
      title: 'Tips',
      content: 'Please login first!',
      showCancel: false,
      success: (res) => {
        wx.switchTab({
          url: '/pages/mine/mine',
        });
      },
      fail: (res) => {
        console.log("Error in pop-up window!");
      },
    });
  },

  // Jump to the operation interface
  gotoOperate() {
    let that = this;
    if (wx.getStorageSync('nickname') != "" && wx.getStorageSync('avatarUrl') != "") { // If the user information exists in the local cache, it indicates that the user has been authorized to log in. If it does not exist, a window will pop up to prompt to log in and jump to the login interface
      wx.navigateTo({
        url: '', //Jump to the operation interface
      })
    } else {
      that.gotoMine(); // Jump to login page
    }
  },

})

4. page/myinfo (fill in personal information)

It is better to have a tool class for global capture to avoid having to verify every request. In addition, the cache needs to be emptied after the token fails

myinfo.js

const app = getApp();

Page({
  /**
   * Initial data of the page
   */
  data: {
    name_vc: '', // User name
    phone_vc: '', // phone number
    address_vc: '', // User address
  },

  // Error reporting function
  showModal(error) {
    wx.showModal({
      content: error.msg,
      showCancel: false,
    })
  },

  /**
   * Form submission
   */
  saveVcData(e) {
    let that = this;
    let values = e.detail.value;
    console.log("form It happened submit Event, the data carried is:", e.detail.value);
    const params = e.detail.value;

    // Button disabled
    that.setData({
      diabled: true,
    });

    // Submit to backend
    wx.request({
      method: "PUT",
      url: 'http://localhost:8888/user/person-info/update-person-info',
      header: {
        'content-type': 'application/json',
        ['Authorization']: wx.getStorageSync('token'),
      },
      dataType: 'json',
      data: JSON.stringify({
        name: e.detail.value.name,
        phone: e.detail.value.phone,
        address: e.detail.value.address,
      }),

      success: (res) => {
        console.log(res);
        if(res.data.code === 401){
          wx.clearStorageSync(), //wipe cache 
          wx.showToast({
            title: 'Login has expired, please login again!',
            icon: 'none',
            duration: 2000,
          })
        }else{
          wx.showModal({
            content: "Submitted successfully",
            showCancel: false,
            success (res) {
              if (res.confirm) {
                // Empty form content
                that.setData({
                  name_vc: '', // User name
                  phone_vc: '', // phone number
                  address_vc: '', // User address
                })
              }
            }
          })
        }
      },
      fail: (res) => {
        wx.showToast({
          title: 'Submission failed, please check the network!',
          icon: 'none',
          duration: 2000,
        })
      }
    })
  },

})

4, Testing

1. Silent login

That is, when the user enters the applet, call[“ http://localhost:8888/user/index/login](http://localhost:8888/user/index/login "“ http://localhost:8888/user/index/login ”)"Interface

2. User not logged in

Click OK to jump to the login page

3. Authorized login

Click reject

Click allow

Authorized login succeeded!

4. Update information

The token is not invalid

Token invalidation (usually token expiration)

Attachment: a common error report

This error is caused by the inconsistency between the front and rear appid and serct mentioned above, and the front end cannot be modified directly in the interface. You need to create a new applet to use the correct appid and serct (good dog applet).

summary

This is the end of the article. At the end of the article, I put a small welfare. The following is a learning idea and direction on front-end development sorted out by Xiaobian in the learning process. To engage in Internet development, the most important thing is to learn technology well, and learning technology is a slow, long and arduous road. It can't be learned by passion for a moment, nor can it be learned by boiling for a few days and nights. We must develop the habit of studying hard at ordinary times, and need more accurate learning direction to achieve effective learning results.

Because there are many contents, only a general outline is put on, which requires more and detailed study of mind mapping Click my GitHub for free.
There is also a full set of free advanced web video tutorials, front-end architecture H5 vue node applet Video + data + code + interview questions!

We also discuss the problems of advanced web technology and solve the problems of advanced web technology.

Keywords: Front-end html Interview Mini Program

Added by ntsf on Sat, 26 Feb 2022 13:16:50 +0200