Cenários de uso comuns e soluções para desenvolvimento de contas oficiais Java

Soluções de cenário de uso comum para desenvolvimento de conta oficial Java

Introdução ao artigo

Este artigo resume muitas funções comuns relacionadas ao desenvolvimento do WeChat que encontrei durante o processo de desenvolvimento.Essas perguntas são respostas dispersas na Internet, então vou resumi-las aqui para facilitar o desenvolvimento subsequente. Se houver erros, ainda espero criticar e apontar, vamos começar agora.

Inclui principalmente os seguintes tutoriais:

  • Crie um gerador de código e integre o Swagger
  • Configuração da interface WeChat, verificação de token de resposta enviada pelo WeChat
  • Atualizar automaticamente o prazo de validade e atualizar o Token ao obter o Token
  • Java lida com mensagens comuns e mensagens de eventos do WeChat e responde de acordo
  • Java enviar mensagem de modelo
  • Login de autorização do WeChat de acesso H5

Você também pode visualizar o endereço do código-fonte do projeto: Enviando e redirecionando mensagens do modelo WeChat: Use Java para enviar mensagens do modelo WeChat e clique na mensagem do modelo para ir para os detalhes (gitee.com)

Preparação

preparar banco de dados

Crie uma nova tabela de tokens

campo tipo observação
eu ia interno chave primária
símbolo varchar símbolo
expira varchar Expiração

Criar declaração de tabela

/*
 Navicat Premium Data Transfer

 Source Server         : localhost
 Source Server Type    : MySQL
 Source Server Version : 80019
 Source Host           : localhost:3306
 Source Schema         : wxtemplate

 Target Server Type    : MySQL
 Target Server Version : 80019
 File Encoding         : 65001

 Date: 30/05/2023 17:47:36
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for w_token
-- ----------------------------
DROP TABLE IF EXISTS `w_token`;
CREATE TABLE `w_token`  (
  `id` int(0) NOT NULL AUTO_INCREMENT,
  `token` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'toekn',
  `expires` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '过期时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of w_token
-- ----------------------------
INSERT INTO `w_token` VALUES (1, 'initvalue', '2023-01-01 00:00:01');
INSERT INTO `w_token` VALUES (2, 'initvalue', '2023-01-01 00:00:01');

SET FOREIGN_KEY_CHECKS = 1;

Aqui, dois dados são adicionados primeiro por padrão e, em seguida, os dados com id igual a 1 serão consultados fixamente ao atualizar e obter o token

adicionar dependências

Abaixo está meu arquivo pom

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>java-demo</artifactId>
    <version>1.0-SNAPSHOT</version>

    <parent>
        <artifactId>spring-boot-starter-parent</artifactId>
        <groupId>org.springframework.boot</groupId>
        <version>2.3.10.RELEASE</version>
    </parent>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--mybatis-plus-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.0.5</version>
        </dependency>
        <!-- velocity 模板引擎, Mybatis Plus 代码生成器需要 -->
        <dependency>
            <groupId>org.apache.velocity</groupId>
            <artifactId>velocity-engine-core</artifactId>
            <version>2.0</version>
        </dependency>
        <!--junit-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
        </dependency>
        <!--druid-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.2.5</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.22</version>
        </dependency>

        <!--lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <!--swagger-->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.9.2</version>
        </dependency>
        <!--swagger ui-->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.9.2</version>
        </dependency>
        <!--java-jwt-->
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.8.2</version>
        </dependency>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.5.9</version>
        </dependency>
    </dependencies>

</project>

editar arquivo de configuração

aplicativo.yml

server:
  port: 8003

spring:
  application:
    name: service-edu
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: GMT+8
  profiles:
    active: dev

aplicativo-dev.yml

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/wxtemplate?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
    username: root
    password: abc123
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      initial-size: 5 # 初始化连接池中连接数量为5
      min-idle: 2 # 最小空闲连接数为2,即连接池中最少保持2个空闲连接不被释放
      max-active: 20 # 最大活跃连接数为20,即连接池中最多同时存在20个活跃连接
      test-on-borrow: true # 每次获取连接时是否进行连接测试,默认为true。如果设置为true,则在从连接池中获取连接时,先执行validation-query配置的测试SQL语句,判断连接是否可用,若不可用则重新创建连接。
      validation-query: select 1 from dual # 连接测试SQL语句,用于检测连接是否可用,在上述的test-on-borrow为true时生效。这里的SQL语句是SELECT 1 FROM DUAL,DUAL表是Oracle数据库中自带的一个虚拟表名,该语句的作用是返回一个固定值1,以此来测试连接是否正常。

wx:
  appid: wx2188729b190d357d #微信公众号appid
  secret: d976b0e6262b829ba003e9a24032447c #微信公众号AppSecret
  template_id: 1B1nMIck2SmkVJOHo_3VVQbyVPVlMItK9al46qsLjE0 # 跟进提醒
  check_token: fawu123456 # 响应微信请求用到的token

Crie uma classe de configuração

Crie um novo config para colocar o arquivo de configuração do nosso projeto

A estrutura do diretório é a seguinte

imagem-20230531085338122

Configuração do aplicativo

Esta configuração é usada principalmente para configurar ComponentScan e MapperScan

package com.szx.java.config;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

/**
 * @author songzx
 * @create 2023-05-30 16:03
 */
@Configuration
@ComponentScan(basePackages = "com.szx")
@MapperScan(basePackages = "com.szx")
public class ApplicationConfig {
    
    
}

SwaggerConfig

Esta configuração é usada para configurar o Swagger

package com.szx.java.config;

import com.google.common.base.Predicates;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

/**
 * @author songzx
 * @create 2022-09-22 11:21
 */
@Configuration
@EnableSwagger2
public class SwaggerConfig {
    
    
    @Bean
    public Docket webApiConfig(){
    
    
        return new Docket(DocumentationType.SWAGGER_2)
                .groupName("webApi")
                .apiInfo(webApiInfo())
                .select()
                .paths(Predicates.not(PathSelectors.regex("/admin/.*")))
                .paths(Predicates.not(PathSelectors.regex("/error.*")))
                .build();
    }

    public ApiInfo webApiInfo(){
    
    
        return new ApiInfoBuilder()
                .title("Api文档")
                .description("文本档描述了定义的接口")
                .version("1.0")
                .contact(new Contact("szx", "https://blog.csdn.net/SongZhengxing_?spm=1010.2135.3001.5343","[email protected]"))
                .build();
    }
}

Modifique a classe de inicialização

Por padrão, não podemos nos dizer intuitivamente o endereço em execução do projeto após a inicialização ser bem-sucedida. Através da configuração a seguir, podemos ver intuitivamente o endereço da interface e o endereço do swagger após a operação bem-sucedida

package com.szx.java;

import lombok.SneakyThrows;
import lombok.extern.log4j.Log4j2;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.Environment;

import java.net.InetAddress;

/**
 * @author songzx
 * @create 2023-05-12 14:41
 */
@Log4j2
@SpringBootApplication
public class SzxApplication {
    
    
    @SneakyThrows
    public static void main(String[] args) {
    
    
        ConfigurableApplicationContext application = SpringApplication.run(SzxApplication.class, args);
        Environment env = application.getEnvironment();
        String host = InetAddress.getLocalHost().getHostAddress();
        String port = env.getProperty("server.port");
        log.info("\n    ----------------------------------------------------------\n\t" +
                        "Application '{}' 正在运行中... Access URLs:\n\t" +
                        "Local: \t\thttp://localhost:{}\n\t" +
                        "External: \thttp://{}:{}\n\t" +
                        "Doc: \thttp://{}:{}/doc.html\n\t" +
                        "SwaggerDoc: \thttp://{}:{}/swagger-ui.html\n\t" +
                        "----------------------------------------------------------",
                env.getProperty("spring.application.name"),
                env.getProperty("server.port"),
                host, port,
                host, port,
                host, port);
    }
}

Efeito de inicialização:

imagem-20230531085613569

Adicionar gerador de código

Copie o código a seguir diretamente para seu arquivo de teste e você poderá gerar automaticamente controlador, entidade, mapeador, serviço

Nota: Altere o local do código gerado para o endereço do seu próprio projeto

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
import com.baomidou.mybatisplus.generator.config.GlobalConfig;
import com.baomidou.mybatisplus.generator.config.PackageConfig;
import com.baomidou.mybatisplus.generator.config.StrategyConfig;
import com.baomidou.mybatisplus.generator.config.rules.DateType;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import org.junit.Test;

/**
 * @author
 * @since 2018/12/13
 */
public class CodeGenerator {
    
    

    @Test
    public void run() {
    
    

        // 1、创建代码生成器
        AutoGenerator mpg = new AutoGenerator();

        // 2、全局配置
        GlobalConfig gc = new GlobalConfig();
        String projectPath = System.getProperty("user.dir");
        gc.setOutputDir("D:\\mygitee\\项目大全\\Java玩转微信模板消息\\wxtemplatemsg\\java-demo\\src\\main\\java");
        gc.setAuthor("szx");
        gc.setOpen(false); //生成后是否打开资源管理器
        gc.setFileOverride(false); //重新生成时文件是否覆盖
        gc.setServiceName("%sService");    //去掉Service接口的首字母I
        gc.setIdType(IdType.ID_WORKER); //主键策略
        gc.setDateType(DateType.ONLY_DATE);//定义生成的实体类中日期类型
        gc.setSwagger2(true);//开启Swagger2模式

        mpg.setGlobalConfig(gc);

        // 3、数据源配置
        DataSourceConfig dsc = new DataSourceConfig();
        dsc.setUrl("jdbc:mysql://127.0.0.1:3306/wxtemplate?serverTimezone=GMT%2B8&useUnicode=yes&characterEncoding=utf8");
        dsc.setDriverName("com.mysql.cj.jdbc.Driver");
        dsc.setUsername("root");
        dsc.setPassword("abc123");
        dsc.setDbType(DbType.MYSQL);
        mpg.setDataSource(dsc);

        // 4、包配置
        PackageConfig pc = new PackageConfig();
        pc.setParent("com.szx"); // 主包名称
        pc.setModuleName("java"); //模块名,生成的结构为:com.szx.edu

        pc.setController("controller");
        pc.setEntity("entity");
        pc.setService("service");
        pc.setMapper("mapper");
        mpg.setPackageInfo(pc);

        // 5、策略配置
        StrategyConfig strategy = new StrategyConfig();
        strategy.setInclude("w_token"); // 数据库表名
        strategy.setNaming(NamingStrategy.underline_to_camel);//数据库表映射到实体的命名策略
        strategy.setTablePrefix(pc.getModuleName() + "_"); //生成实体时去掉表前缀

        strategy.setColumnNaming(NamingStrategy.underline_to_camel);//数据库表字段映射到实体的命名策略
        strategy.setEntityLombokModel(true); // lombok 模型 @Accessors(chain = true) setter链式操作

        strategy.setRestControllerStyle(true); //restful api风格控制器
        strategy.setControllerMappingHyphenStyle(true); //url中驼峰转连字符

        mpg.setStrategy(strategy);


        // 6、执行
        mpg.execute();
    }
}

descrição de endereço

imagem-20230531090028713

imagem-20230531090116022

imagem-20230531090303495

imagem-20230531090340263

Execute o código acima para gerar automaticamente o código de resposta de acordo com os campos do banco de dados

Responda à verificação do token enviada pelo WeChat

Acesso à documentação da plataforma WeChat: Visão geral do acesso | Documentação aberta do WeChat (qq.com)

Olhando a documentação oficial, podemos ver a seguinte descrição

imagem-20230531090930529

Portanto, devemos primeiro escrever uma interface get para responder à solicitação do WeChat e devolvê-la corretamente

WTokenControllerAdicione o código em

@Api(tags = "token管理")
@RestController
@RequestMapping("/wtoken")
public class WTokenController {
    
    

    @Autowired
    WTokenService tokenService;

    @ApiOperation("微信接口配置,响应微信发送的Token验证")
    @GetMapping
    public String checkToken(HttpServletRequest request, HttpServletResponse response){
    
    
        return tokenService.checkToken(request,response);
    }
}

WTokenServiceImplimplementar checkTokeno método em

/**
 * 微信接口配置,响应微信发送的Token验证
 */
@Override
public String checkToken(HttpServletRequest request, HttpServletResponse response) {
    
    
    if (StringUtils.isNotBlank(request.getParameter("signature"))) {
    
    
        String signature = request.getParameter("signature");
        String timestamp = request.getParameter("timestamp");
        String nonce = request.getParameter("nonce");
        String echostr = request.getParameter("echostr");
        if (SignUtil.checkSignature(signature, timestamp, nonce)) {
    
    
            return echostr;
        }
    }
    return "";
}

SignUtilUma classe de ferramenta de assinatura é usada aqui , então crie uma nova utils/SignUtil.java, o conteúdo é o seguinte

package com.szx.java.utils;

import com.szx.java.constants.WxConstants;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;

/**
 * @author songzx
 * @create 2022-11-22 13:52
 */
public class SignUtil {
    
    
    // 与开发模式接口配置信息中的Token保持一致.
    private static String token = WxConstants.WX_CHECK_TOKEN;

    /**
     * 校验签名
     * @param signature 微信加密签名.
     * @param timestamp 时间戳.
     * @param nonce 随机数.
     * @return
     */
    public static boolean checkSignature(String signature, String timestamp, String nonce) {
    
    
        // 对token、timestamp、和nonce按字典排序.
        String[] paramArr = new String[] {
    
    token, timestamp, nonce};
        Arrays.sort(paramArr);

        // 将排序后的结果拼接成一个字符串.
        String content  = paramArr[0].concat(paramArr[1]).concat(paramArr[2]);

        String ciphertext = null;
        try {
    
    
            MessageDigest md = MessageDigest.getInstance("SHA-1");
            // 对拼接后的字符串进行sha1加密.
            byte[] digest = md.digest(content.toString().getBytes());
            ciphertext = byteToStr(digest);
        } catch (NoSuchAlgorithmException e) {
    
    
            e.printStackTrace();
        }

        // 将sha1加密后的字符串与signature进行对比.
        return ciphertext != null ? ciphertext.equals(signature.toUpperCase()) : false;
    }

    /**
     * 将字节数组转换为十六进制字符串.
     * @param byteArray
     * @return
     */
    private static String byteToStr(byte[] byteArray) {
    
    
        String strDigest = "";
        for (int i = 0; i < byteArray.length; i++) {
    
    
            strDigest += byteToHexStr(byteArray[i]);
        }
        return strDigest;
    }

    /**
     * 将字节转换为十六进制字符串.
     * @param mByte
     * @return
     */
    private static String byteToHexStr(byte mByte) {
    
    
        char[] Digit = {
    
     '0', '1' , '2', '3', '4' , '5', '6', '7' , '8', '9', 'A' , 'B', 'C', 'D' , 'E', 'F'};
        char[] tempArr = new char[2];
        tempArr[0] = Digit[(mByte >>> 4) & 0X0F];
        tempArr[1] = Digit[mByte & 0X0F];

        String s = new String(tempArr);
        return s;
    }
}

Reinicie o projeto, abra swagger-ui e veja a interface que escrevemos

imagem-20230531091538462

Por ser desenvolvido localmente, o projeto só pode ser acessado localmente. Mas o WeChat não pode acessar nossa máquina local, então podemos usar a ferramenta de penetração da intranet para mapear o endereço IP local para o acesso à rede pública

Aqui estou usando o natapp, o site oficial está aqui , você mesmo pode estudar. A seguir está o endereço da rede pública após minha penetração

imagem-20230531091933376

Em seguida, abra a plataforma de teste WeChat, copie o endereço da rede pública + nome da interface para a caixa de entrada abaixo

imagem-20230531092124294

Após clicar em Enviar, se tudo estiver normal, um lembrete de configuração bem-sucedida aparecerá

imagem-20230531092134909

Neste ponto, concluímos o trabalho de acesso ao WeChat, vamos aprender como enviar mensagens modelo

Encapsular classe de resposta pública

Primeiro prepare uma classe de enumeração de código de status

package com.szx.java.utils;

/**
 * @author songzx
 * @date 2023/6/4
 * @apiNote
 */
public enum ResponseEnum {
    
    
    // 可以根据自己的实际需要增加状态码
    SUCCESS("0", "ok"),
    SERVER_INNER_ERR("500","系统繁忙"),
    PARAM_LACK("100" , "非法参数"),
    OPERATION_FAILED("101" ,"操作失败");

    private String code;
    private String msg;

    ResponseEnum(String code, String msg) {
    
    
        this.code = code;
        this.msg = msg;
    }

    public String getCode() {
    
    
        return code;
    }

    public void setCode(String code) {
    
    
        this.code = code;
    }

    public String getMsg() {
    
    
        return msg;
    }

    public void setMsg(String msg) {
    
    
        this.msg = msg;
    }
}

e então a classe de entidade de resposta

package com.szx.java.utils;

import com.fasterxml.jackson.annotation.JsonInclude;

/**
 * @author songzx
 * @date 2023/6/4
 * @apiNote
 */
@JsonInclude(JsonInclude.Include.NON_NULL) // 值等于null的属性不返回
public class Response<T> {
    
    
    private String code;

    private String msg;

    private T data;


    /**
     * @title 成功消息
     * @return
     */
    public static <T> Response<T> success() {
    
    
        return rspMsg(ResponseEnum.SUCCESS);
    }

    /**
     * @title 失败消息
     * @return
     */
    public static <T> Response<T> error() {
    
    
        return rspMsg(ResponseEnum.SERVER_INNER_ERR);
    }

    /**
     * @title 自定义消息
     * @return
     */
    public static <T> Response<T> rspMsg(ResponseEnum responseEnum) {
    
    
        Response<T> message = new Response<T>();
        message.setCode(responseEnum.getCode());
        message.setMsg(responseEnum.getMsg());
        return message;
    }

    /**
     * @title 自定义消息
     * @return
     */
    public static <T> Response<T> rspMsg(String code , String msg) {
    
    
        Response<T> message = new Response<T>();
        message.setCode(code);
        message.setMsg(msg);
        return message;
    }

    /**
     * @title 返回数据
     * @param data
     * @return
     */
    public static <T> Response<T> rspData(T data) {
    
    
        Response<T> responseData = new Response<T>();
        responseData.setCode(ResponseEnum.SUCCESS.getCode());
        responseData.setData(data);
        return responseData;
    }

    /**
     * @title 返回数据-自定义code
     * @param data
     * @return
     */
    public static <T> Response<T> rspData(String code , T data) {
    
    
        Response<T> responseData = new Response<T>();
        responseData.setCode(code);
        responseData.setData(data);
        return responseData;
    }


    public String getCode() {
    
    
        return code;
    }

    public void setCode(String code) {
    
    
        this.code = code;
    }

    public String getMsg() {
    
    
        return msg;
    }

    public void setMsg(String msg) {
    
    
        this.msg = msg;
    }

    public T getData() {
    
    
        return data;
    }

    public void setData(T data) {
    
    
        this.data = data;
    }
}

Obtenha token de acesso

Documentação aberta do WeChat (qq.com)

O access_token é a credencial de chamada de interface globalmente exclusiva da conta oficial, e o access_token é necessário quando a conta oficial chama cada interface. Os desenvolvedores precisam salvar adequadamente. O armazenamento do access_token deve reservar no mínimo 512 caracteres. O período de validade do access_token é atualmente de 2 horas e precisa ser atualizado regularmente. Obtê-lo repetidamente invalidará o access_token obtido da última vez.

adicionar método get

@GetMapping("getAccessToken")
public Response<String> gotAccessToken(){
    
    
    String accessToken = tokenService.getAccessToken();
    return Response.rspData(accessToken);
}

Implemente o método getAccessToken em tokenService

/**
     * 获取AccessToken
     * @return
     */
@Override
public String getAccessToken() {
    
    
    // 固定查询id等于1的数据
    WToken wToken = this.getById(1);
    // 判断当前的accessToken是否在有效期内,小于0表示在有效期内
    if(DateTimeUtils.CompareTime(wToken.getExpires()) < 0){
    
    
        return wToken.getToken();
    }else{
    
    
        // 不再有效期内时调用接口获取新的token
        String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&" +
            "appid=" + WxConstants.WX_APPID +
            "&secret=" + WxConstants.WX_SECRET;
        // 发送请求,获取json格式字符串
        String result_str = HttpUtil.get(url);
        // 将json格式的字符串转成JSONObject类型,方便获取里面的数据
        JSONObject result_json = JSONUtil.parseObj(result_str);
        // 从json中读取access_token字段,并转成string
        String access_token = result_json.get("access_token").toString();
        // 更新token
        wToken.setToken(access_token);
        // 更新过期时间
        wToken.setExpires(DateTimeUtils.FutureTime());
        // 更新数据库中的值
        this.updateById(wToken);
        // 返回最新的token
        return wToken.getToken();
    }
}

Código da classe da ferramenta DateTimeUtils usado aqui

package com.szx.java.utils;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Date;

/**
 * @author songzx
 * @date 2023/6/4
 * @apiNote
 */
public class DateTimeUtils {
    
    
    /**
     * 拿当前时间和传递过来时间做比较,如果当前时间小于传递进来的时间,则返回负数,否则返回正数
     */
    public static int CompareTime(String time){
    
    
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        LocalDateTime dateTime = LocalDateTime.parse(time, formatter);
        Instant instant = dateTime.atZone(ZoneId.systemDefault()).toInstant();
        long timestamp = instant.toEpochMilli();

        long currentTimestamp = System.currentTimeMillis();
        return Long.compare(currentTimestamp, timestamp);
    }

    /**
     * 更新过期时间
     */
    public static String FutureTime(){
    
    
        int seconds = 7000;
        LocalDateTime dateTime = LocalDateTime.now().plusSeconds(seconds);
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        return dateTime.format(formatter);
    }
}

Responder às solicitações do WeChat

Mensagem de texto | Documento aberto do WeChat (qq.com)

Quando um usuário comum do WeChat envia uma mensagem para uma conta pública, o servidor WeChat envia o pacote de dados XML da mensagem POST para a URL preenchida pelo desenvolvedor.

importar processamento de dependência xml

<!--处理xml-->
<dependency>
    <groupId>xmlpull</groupId>
    <artifactId>xmlpull</artifactId>
    <version>1.1.3.1</version>
</dependency>
<!--XML解析器-->
<dependency>
    <groupId>dom4j</groupId>
    <artifactId>dom4j</artifactId>
    <version>1.6</version>
</dependency>

Adicione uma classe de ferramenta

package com.szx.java.utils;

import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import javax.servlet.http.HttpServletRequest;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

/**
 * 封装和处理xml文件
 * @author Administrator
 *
 */
public class XmlUtil {
    
    

    private static final String PREFIX_XML = "<xml>";

    private static final String SUFFIX_XML = "</xml>";

    private static final String PREFIX_CDATA = "<![CDATA[";

    private static final String SUFFIX_CDATA = "]]>";

    /**
     * 转化成xml, 单层无嵌套
     */
    public static String xmlFormat(Map<String, String> parm, boolean isAddCDATA) {
    
    

        StringBuffer strbuff = new StringBuffer(PREFIX_XML);
        if (CollectionUtil.isNotEmpty(parm)) {
    
    
            for (Entry<String, String> entry : parm.entrySet()) {
    
    
                strbuff.append("<").append(entry.getKey()).append(">");
                if (isAddCDATA) {
    
    
                    strbuff.append(PREFIX_CDATA);

                    if (StringUtils.isNotEmpty(entry.getValue())) {
    
    
                        strbuff.append(entry.getValue());
                    }
                    strbuff.append(SUFFIX_CDATA);
                } else {
    
    
                    if (StringUtils.isNotEmpty(entry.getValue())) {
    
    
                        strbuff.append(entry.getValue());
                    }
                }
                strbuff.append("</").append(entry.getKey()).append(">");
            }
        }
        return strbuff.append(SUFFIX_XML).toString();
    }

    /**
     * 解析微信发来的请求(XML)
     *
     * @param request
     * @return
     * @throws Exception
     */
    public static Map<String, String> parseXml(HttpServletRequest request) throws Exception {
    
    
        // 将解析结果存储在HashMap中
        Map<String, String> map = new HashMap<String, String>();

        // 从request中取得输入流
        InputStream inputStream = request.getInputStream();
        // 读取输入流
        SAXReader reader = new SAXReader();
        Document document = reader.read(inputStream);
        // 得到xml根元素
        Element root = document.getRootElement();
        // 得到根元素的所有子节点

        @SuppressWarnings("unchecked")
        List<Element> elementList = root.elements();

        // 遍历所有子节点
        for (Element e : elementList) {
    
    
            map.put(e.getName(), e.getText());
        }
        // 释放资源
        inputStream.close();
        inputStream = null;
        return map;
    }
}

Adicione um método POST para responder ao WeChat. O endereço deste método deve ser o mesmo do token de verificação configurado acima, exceto que o tipo de solicitação é alterado para postagem.

@ApiOperation("相应微信消息")
@PostMapping
public String postWeChar(HttpServletRequest request, HttpServletResponse response){
    
    
    return tokenService.postWeChar(request,response);
}

Implementar o método postWeChar

/**
 * 相应微信请求
 * @param request
 * @param response
 * @return
 */
@Override
public String postWeChar(HttpServletRequest request, HttpServletResponse response) {
    
    
    try {
    
    
        Map<String, String> xmlMap = XmlUtil.parseXml(request);
        System.out.println(xmlMap);
    } catch (Exception e) {
    
    
        e.printStackTrace();
    }
    return null;
}

Por exemplo, agora enviamos uma mensagem para a conta oficial

imagem-20230604182124942

Você pode ver que uma mensagem foi impressa e podemos avaliar o tipo de mensagem de acordo com MsgType. Obtenha o openid do usuário e outras operações

Podemos criar uma classe de mapeamento MessageType de acordo com o tipo de mensagem

package com.szx.java.utils;

/**
 * @author songzx
 * @date 2023/6/4
 * @apiNote
 */
public class MessageType {
    
    
    /*
     * 文本消息
     */
    public static final String TEXT_MESSAGE = "text";
    /*
     * 图片消息
     */
    public static final String IMAGE_MESSAGE = "image";
    /*
     * 语音消息
     */
    public static final String VOICE_MESSAGE = "voice";
    /*
     * 视频消息
     */
    public static final String VIDEO_MESSAGE = "video";
    /*
     * 小视频消息消息
     */
    public static final String SHORTVIDEO_MESSAGE = "shortvideo";
    /*
     * 地理位置消息
     */
    public static final String POSOTION_MESSAGE = "location";
    /*
     * 链接消息
     */
    public static final String LINK_MESSAGE = "link";
    /*
     * 音乐消息
     */
    public static final String MUSIC_MESSAGE = "music";
    /*
     * 图文消息
     */
    public static final String IMAGE_TEXT_MESSAGE = "news";
    /*
     * 请求消息类型:事件推送
     */
    public static final String REQ_MESSAGE_TYPE_EVENT = "event";
    /*
     * 事件类型:subscribe(订阅)
     */
    public static final String EVENT_TYPE_SUBSCRIBE = "subscribe";
    /*
     * 事件类型:unsubscribe(取消订阅)
     */
    public static final String EVENT_TYPE_UNSUBSCRIBE = "unsubscribe";
    /*
     * 事件类型:scan(用户已关注时的扫描带参数二维码)
     */
    public static final String EVENT_TYPE_SCAN = "scan";
    /*
     * 事件类型:LOCATION(上报地理位置)
     */
    public static final String EVENT_TYPE_LOCATION = "location";
    /*
     * 事件类型:CLICK(自定义菜单)
     */
    public static final String EVENT_TYPE_CLICK = "click";

    /*
     * 响应消息类型:文本
     */
    public static final String RESP_MESSAGE_TYPE_TEXT = "text";
    /*
     * 响应消息类型:图片
     */
    public static final String RESP_MESSAGE_TYPE_IMAGE = "image";
    /*
     * 响应消息类型:语音
     */
    public static final String RESP_MESSAGE_TYPE_VOICE = "voice";
    /*
     * 响应消息类型:视频
     */
    public static final String RESP_MESSAGE_TYPE_VIDEO = "video";
    /*
     * 响应消息类型:音乐
     */
    public static final String RESP_MESSAGE_TYPE_MUSIC = "music";
    /*
     * 响应消息类型:图文
     */
    public static final String RESP_MESSAGE_TYPE_NEWS = "news";

}

Adicione WeCharServiceImpl para lidar com mensagens e eventos enviados pelo WeChat

package com.szx.java.service.impl;

import com.szx.java.utils.MessageType;
import com.szx.java.utils.XmlUtil;
import org.springframework.stereotype.Service;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * @author songzx
 * @date 2023/6/4
 * @apiNote
 */
@Service
public class WeCharServiceImpl {
    
    
    // 处理微信发来的请求 map 消息业务处理分发
    public String parseMessage(Map<String, String> map) {
    
    
        String respXml = null;
        try {
    
    
            // 发送方帐号
            String fromUserName = map.get("FromUserName");
            // 开发者微信号
            String toUserName = map.get("ToUserName");
            // 取得消息类型
            String MsgType = map.get("MsgType");

            // 发现直接把要返回的信息直接封装成replyMap集合,然后转换成 xml文件,是不是实体类可以不用了
            Map<String, String> replyMap = new HashMap<String, String>();
            replyMap.put("ToUserName", fromUserName);
            replyMap.put("FromUserName", toUserName);
            replyMap.put("CreateTime", String.valueOf(new Date().getTime()));
            if (MsgType.equals(MessageType.TEXT_MESSAGE)) {
    
    
                // 用map集合封装
                replyMap.put("MsgType", MessageType.RESP_MESSAGE_TYPE_TEXT);
                replyMap.put("Content", "您发送的是文本消息");
                respXml = XmlUtil.xmlFormat(replyMap, true);
            } else if (MsgType.equals(MessageType.IMAGE_MESSAGE)) {
    
    
                // 以下方式根据需要来操作
                replyMap.put("MsgType", MessageType.RESP_MESSAGE_TYPE_TEXT);
                replyMap.put("Content", "您发送的是图片消息");
                respXml = XmlUtil.xmlFormat(replyMap, true);
            } else if (MsgType.equals(MessageType.VOICE_MESSAGE)) {
    
    
                // 以下方式根据需要来操作
                replyMap.put("MsgType", MessageType.RESP_MESSAGE_TYPE_TEXT);
                replyMap.put("Content", "您发送的是语音消息");
                respXml = XmlUtil.xmlFormat(replyMap, true);
            } else if (MsgType.equals(MessageType.VIDEO_MESSAGE)) {
    
    
                replyMap.put("MsgType", MessageType.RESP_MESSAGE_TYPE_TEXT);
                replyMap.put("Content", "您发送的是视频消息");
                respXml = XmlUtil.xmlFormat(replyMap, true);
            } else if (MsgType.equals(MessageType.SHORTVIDEO_MESSAGE)) {
    
    
                replyMap.put("MsgType", MessageType.RESP_MESSAGE_TYPE_TEXT);
                replyMap.put("Content", "您发送的是小视频消息");
                respXml = XmlUtil.xmlFormat(replyMap, true);
            } else if (MsgType.equals(MessageType.POSOTION_MESSAGE)) {
    
    
                replyMap.put("MsgType", MessageType.RESP_MESSAGE_TYPE_TEXT);
                replyMap.put("Content", "您发送的是地理位置消息");
                respXml = XmlUtil.xmlFormat(replyMap, true);
            } else if (MsgType.equals(MessageType.LINK_MESSAGE)) {
    
    
                replyMap.put("MsgType", MessageType.RESP_MESSAGE_TYPE_TEXT);
                replyMap.put("Content", "您发送的是链接消息");
                respXml = XmlUtil.xmlFormat(replyMap, true);
            }
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
        return respXml;
    }

    // 事件消息业务分发
    public String parseEvent(Map<String, String> map) {
    
    
        String respXml = null;
        try {
    
    
            // 发送方帐号
            String fromUserName = map.get("FromUserName");
            // 开发者微信号
            String toUserName = map.get("ToUserName");
            // 取得消息类型
            String MsgType = map.get("MsgType");
            //获取事件类型
            String eventType = map.get("Event");

            // 发现直接把要返回的信息直接封装成replyMap集合,然后转换成 xml文件,是不是实体类可以不用了
            Map<String, String> replyMap = new HashMap<String, String>();
            replyMap.put("ToUserName", fromUserName);
            replyMap.put("FromUserName", toUserName);
            replyMap.put("CreateTime", String.valueOf(new Date().getTime()));
            if (eventType.equals(MessageType.EVENT_TYPE_SUBSCRIBE)) {
    
    // 关注
                // 用map集合封装
                replyMap.put("MsgType", MessageType.RESP_MESSAGE_TYPE_TEXT);
                replyMap.put("Content", "欢迎关注");
                respXml = XmlUtil.xmlFormat(replyMap, true);
            }
            if (eventType.equals(MessageType.EVENT_TYPE_UNSUBSCRIBE)) {
    
    // 取消关注

            }
            if (eventType.equals(MessageType.EVENT_TYPE_SCAN)) {
    
    // 用户已关注时的扫描带参数二维码

            }
            if (eventType.equals(MessageType.EVENT_TYPE_LOCATION)) {
    
    // 上报地理位置

            }
            if (eventType.equals(MessageType.EVENT_TYPE_CLICK)) {
    
    // 自定义菜单

            }
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
        return respXml;
    }
}

Injetar weCharService automaticamente

@Autowired
WeCharServiceImpl weCharService;

Chame esses dois métodos em resposta a

/**
 * 相应微信请求
 * @param request
 * @param response
 * @return
 */
@Override
public String postWeChar(HttpServletRequest request, HttpServletResponse response) {
    
    
    try {
    
    
        // 解析request中的xml得到一个map
        Map<String, String> xmlMap = XmlUtil.parseXml(request);
        // 判断是否事件类型
        String eventType = xmlMap.get("Event");
        // 判断map中是否存在Event,由此判断这个事件是普通消息还是事件推送
        if(StringUtils.isNotEmpty(eventType)){
    
    
            // 事件推送
            return weCharService.parseEvent(xmlMap);
        }else{
    
    
            // 普通消息
            return weCharService.parseMessage(xmlMap);
        }
    } catch (Exception e) {
    
    
        e.printStackTrace();
    }
    return "";
}

Efeito:

imagem-20230604190827452

Quando eu parar de seguir e seguir novamente, uma nova mensagem também será enviada, e o método de mensagem do evento será chamado aqui

imagem-20230604191036499

enviar modelo de mensagem

Plataforma pública WeChat (qq.com) - enviar modelo de mensagem

Gere um ID de mensagem de modelo antes de enviar a mensagem de modelo

imagem-20230604214518783

Conteúdo da mensagem, xxxx.DATA, o seguinte .DATA foi corrigido

O modelo de mensagem no ambiente de teste pode ser customizado, mas ao solicitar um modelo de mensagem na conta oficial oficial, o modelo de mensagem só pode ser selecionado de acordo com a categoria da conta oficial, e o modelo de mensagem não pode ser customizado, e o os campos são palavra-chave1 e palavra-chave2 , ..., portanto, sugere-se que também usemos palavra-chave1 e palavra-chave2 para definir os campos da mensagem do modelo no ambiente de teste

{
   
   {first.DATA}} 
客户姓名:{
   
   {keyword1.DATA}} 
联系电话:{
   
   {keyword2.DATA}} 
业务类型:{
   
   {keyword3.DATA}} 
{
   
   {remark.DATA}}

De acordo com o documento, queremos enviar uma solicitação de postagem e passar os dados json, criamos uma classe de entidade correspondente com base nos dados json, o que é conveniente para definir parâmetros

Adicionar WxTemplateVo

package com.szx.java.entity.Vo;

import lombok.Data;

import java.util.TreeMap;

/**
 * @author songzx
 * @create 2022-11-29 15:45
 */
@Data
public class WxTemplateVo {
    
    
    /**
     * 接收者openId
     */
    private String touser;
    /**
     * 模板ID
     */
    private String template_id;
    /**
     * 模板跳转链接
     */
    private String url;

    /**
     * data数据
     */
    private TreeMap<String, TreeMap<String, String>> data;

    /**
     * 参数
     *
     * @param value 值
     * @param color 颜色 可不填
     * @return params
     */
    public static TreeMap<String, String> item(String value, String color) {
    
    
        TreeMap<String, String> params = new TreeMap<String, String>();
        params.put("value", value);
        params.put("color", color);
        return params;
    }
}

Implementar método para enviar mensagem modelo

/**
 * 发送模板消息
 */
@Override
public void sendTemplateMsg() {
    
    
    // 发送模板请求的地址
    String postUrl = "https://api.weixin.qq.com/cgi-bin/message/template/send" +
            "?access_token=" + getToken();
    // 要给那个用户发送模板消息
    String openId = "olttN6WJOYe-lTysV8_tsnZ7-HMQ";
    // 模板消息ID
    String templeID = "vRGjpYZ-uL3CCREW9c6Kl9csekoW9tVbVl7hf_y3k5U";
    // 点击模板消息要跳转的地址,如果不设置则不会跳转
    String templeUrl = "http://baidu.com";

    // 构造模板消息内容
    TreeMap<String, TreeMap<String, String>> params = new TreeMap<>();
    params.put("keyword1", WxTemplateVo.item("第一行消息", "#409EFF"));
    params.put("keyword2", WxTemplateVo.item("第二行消息", "#409EFF"));
    params.put("keyword3", WxTemplateVo.item("第三行消息", "#409EFF"));

    // 将模板消息放进实体类中
    WxTemplateVo wxTemplateMsg = new WxTemplateVo();
    wxTemplateMsg.setTemplate_id(templeID);
    wxTemplateMsg.setTouser(openId);
    wxTemplateMsg.setData(params);
    wxTemplateMsg.setUrl(templeUrl);

    // 请求请求
    HttpUtil.post(postUrl, JSONUtil.toJsonStr(wxTemplateMsg));
}

Em um cenário de negócios real, quando um determinado negócio é concluído, o código aciona o método de envio de uma mensagem de modelo e obtém dinamicamente o openId do usuário, o ID da mensagem de modelo e o endereço de salto, mas não temos um cenário de negócios real aqui, então elas são variáveis ​​embutidas em código

Vamos escrever uma interface para acionar manualmente o envio de mensagens modelo

@ApiOperation("发送模板消息")
@GetMapping("sendTemplateMsg")
public void sendTemplateMsg(){
    
    
    tokenService.sendTemplateMsg();
}

Após clicar no botão enviar, a conta oficial enviará um modelo de mensagem, o efeito é o seguinte

No ambiente de teste, a cor da fonte que definimos não entrou em vigor. Entrará em vigor na conta pública oficial

imagem-20230604220626246

Acesso H5 Login WeChat

Para etapas detalhadas, consulte meu outro artigo: [Conta pública H5 de acesso à página WeChat login process_Public account h5 WeChat login_szx’s blog of development notes-CSDN blog](https://blog.csdn.net/SongZhengxing_/article /details/121036115?utm_source =uc_fansmsg)

Coloque o código js principal aqui, adicione o seguinte código ao código para realizar a lógica de login do WeChat

De acordo com o seu negócio, você pode fazer uma pequena alteração

// 这里是两个方法,一个
import {
    
     getTokenByCode, getUserinfoByToken } from '../api/index.js'
// 这里使用的是Vue3的Pinia,如果是Vue2可以换成Vuex
import {
    
     userInfo } from '../store/userInfo.js'
// 公众号的appid
const appid = import.meta.env.VITE_APPID
// 公众号的secret
const secret = import.meta.env.VITE_SECRET

// 获取当前页面地址作为回调地址,并且对地址进行urlEncode处理
export function jumpAuthPage() {
    
    
  let url = localStorage.getItem('localUrl')
  url = processUrl(url)
  let local = encodeURIComponent(url)
  // 跳转到授权页面
  window.location.href =
    'https://open.weixin.qq.com/connect/oauth2/authorize?appid=' +
    appid +
    '&redirect_uri=' +
    local +
    '&response_type=code&scope=snsapi_userinfo&state=1#wechat_redirect'
}

// 解析原始URL
function processUrl(url) {
    
    
  if (url.indexOf('code') !== -1) {
    
    
    let start = url.indexOf('?')
    let end = url.indexOf('#')
    return url.slice(0, start) + url.slice(end)
  } else {
    
    
    return url
  }
}

// 获取路径上参数
export function getUrlCode(name) {
    
    
  return (
    decodeURIComponent(
      (new RegExp('[?|&]' + name + '=' + '([^&;]+?)(&|#|;|$)').exec(
        location.href
      ) || [, ''])[1].replace(/\+/g, '%20')
    ) || null
  )
}

// 根据code获取网站授权token
export function getTokenFormCode(code) {
    
    
  // 根据code获取到openid
  getTokenByCode(appid, secret, code).then((res) => {
    
    
    const {
    
     access_token, openid } = res.data.info
    // 根据token获取用户基本信息
    getUserinfoByToken(access_token, openid).then((info) => {
    
    
      if (info.code === 500) {
    
    
        // 如果根据token没有拿到用户信息则重新授权获取新的code重新获取用户信息
        jumpAuthPage()
      } else {
    
    
        // 获取到用户信息后删除缓存的地址
        localStorage.removeItem('localUrl')
        // 保存用户基本信息到Pinia
        userInfo().setUserInfo(info.data.info, info.data)
      }
    })
  })
}

export default function () {
    
    
  // 判断是否有code
  let code = getUrlCode('code')
  let scope = getUrlCode('scope')
  // 当获取到code后再调用获取token的方法
  if (code) {
    
    
    getTokenFormCode(code)
  } else {
    
    
    // 这里使用缓存获取最初进来的页面地址,这样实现在授权完成后回调时还展示最开始的页面,实现从那个页面进来,还回调到那个页面
    if (!scope && !localStorage.getItem('localUrl')) {
    
    
      localStorage.setItem('localUrl', window.location.href)
    }
    jumpAuthPage()
  }
}

Em seguida, importe-o em main.js

import wxauth from '@/utils/wxauth.js'

app.use(wxauth)

Existem dois métodos usados ​​no código acima

  • getTokenByCode
  • getUserinfoByToken
// 根据code获取网站授权token
export function getTokenByCode(appid, secret, code) {
    
    
  return server({
    
    
    method: 'get',
    url: `/edu/wx/oauth2`,
    params: {
    
    
      appid,
      secret,
      code,
    },
  })
}

// 拉取用户信息
export function getUserinfoByToken(token, openid) {
    
    
  return server({
    
    
    method: 'get',
    url: `/edu/wx/wxUserinfo`,
    params: {
    
    
      token,
      openid,
    },
  })
}

Os códigos Java correspondentes são os seguintes

/edu/wx/oauth2

/**
 * 根据code获取网站授权token
 */
@ApiOperation("根据code获取网站授权token")
@GetMapping("oauth2")
public Msg oauth(@RequestParam String appid,
                 @RequestParam String secret,
                 @RequestParam String code) {
    
    
    // 构造请求地址
    String url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=" + appid +
            "&secret=" + secret +
            "&code=" + code +
            "&grant_type=authorization_code";
	// 发送get请求
    JSONObject jsonObject = HttpUtils.DO_GET(url);
    // 将请求信息返回给前端
    return Msg.Ok().data("info", jsonObject);
}

/edu/wx/wxUserinfo

/**
  * 获取用户基本信息
  */
@ApiOperation("获取用户基本信息")
@GetMapping("wxUserinfo")
public Msg getUserInfo(@RequestParam String token,
                       @RequestParam String openid) {
    
    

    // 根据openid查询用户表,判断这个用户是否存在
    LfUser fUser = lfUserService.zcUserByOpenid(openid);
	// 构造请求地址
    String url = "https://api.weixin.qq.com/sns/userinfo?access_token=" + token +
        "&openid=" + openid +
        "&lang=zh_CN";
    // 发送请求
    String s = HttpUtil.get(url);
	// 将得到的结果字符串json转成JSONObject
    cn.hutool.json.JSONObject jsonObject = JSONUtil.parseObj(s);
    // 从结果中获取微信昵称
    String nickname = jsonObject.get("nickname").toString();
    // 从结果中获取微信头像
    String headimgurl = jsonObject.get("headimgurl").toString();
    // 如果该用户不曾存在,则注册用户,否则更新用户最新的微信昵称和头像
    if (fUser == null) {
    
    
        fUser = new LfUser();
        fUser.setOpenid(openid);
        fUser.setNickname(nickname);
        fUser.setPhoto(headimgurl);
        lfUserService.addLfUser(fUser);
    } else {
    
    
        fUser.setNickname(nickname);
        fUser.setPhoto(headimgurl);
        lfUserService.updateById(fUser);
    }
	// 将结果返回
    return Msg.Ok()
        .data("info", fUser)
        .data("userId", fUser.getId())
        .data("loginTime", fUser.getGmtModified());
}

Acho que você gosta

Origin blog.csdn.net/SongZhengxing_/article/details/131038356
Recomendado
Clasificación