一、背景
小程序是一种全新的连接用户与服务的方式,它可以在微信内被便捷地获取和传播,同时具有出色的使用体验。
微信小程序官方文档:https://developers.weixin.qq.com/miniprogram/dev/framework/
二、技术栈
- SpringBoot 2.0
- MyBatis-Plus
- MySQL
- Redis
- Sa-Token
- weixin-java-miniapp
三、实现
3.1 引入依赖
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.17</version>
<relativePath/>
</parent>
<groupId>com.qiangesoft</groupId>
<artifactId>wechat-miniapp</artifactId>
<version>1.0.0</version>
<name>wechat-miniapp</name>
<packaging>jar</packaging>
<description>微信小程序</description>
<properties>
<java.version>1.8</java.version>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
</properties>
<dependencies>
<!-- web模块 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 参数校验模块 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 自动配置 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- sa-token -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
<version>1.37.0</version>
</dependency>
<!-- mysql -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.11</version>
</dependency>
<!-- mybatis-plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3</version>
</dependency>
<!-- knife4j依赖 -->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi2-spring-boot-starter</artifactId>
<version>4.4.0</version>
</dependency>
<!-- fastjson -->
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.34</version>
</dependency>
<!-- hutool工具包 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-core</artifactId>
<version>5.8.27</version>
</dependency>
<!-- 开源weixin-java-miniapp -->
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-miniapp</artifactId>
<version>4.5.0</version>
</dependency>
</dependencies>
<build>
<finalName>wxminiapp</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>com.qiangesoft.miniapp.WxMaApplication</mainClass>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.10.1</version>
<configuration>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
3.2 yml配置
server:
port: 8087
spring:
# 数据库配置
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/wechat_ma?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8
username: root
password: root
# redis配置
redis:
host: localhost
port: 6379
username:
password:
timeout: 2000
lettuce:
pool:
max-active: 8
max-idle: 8
max-wait: 0
min-idle: 0
# mybatis-plus配置
mybatis-plus:
# MyBaits别名包扫描路径
type-aliases-package: com.qiangesoft.miniapp.entity
# Mapper所对应的XML文件位置 默认【classpath*:/mapper/**/*.xml】
mapper-locations: classpath*:/mapper/*Mapper.xml
configuration:
# 日志打印
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 是否开启自动驼峰命名规则
map-underscore-to-camel-case: true
global-config:
db-config:
# 全局默认主键类型
id-type: auto
# 逻辑删除配置
logic-delete-field: deleted
logic-delete-value: 1
logic-not-delete-value: 0
# Sa-Token配置
sa-token:
# token名称(同时也是 cookie 名称)
token-name: satoken
# token有效期(单位:秒)默认30天
timeout: 2592000
# token最低活跃频率(单位:秒)
active-timeout: -1
# 是否允许同一账号多地同时登录
is-concurrent: true
# 在多人登录同一账号时,是否共用一个 token
is-share: true
# token 风格(默认可取值:uuid、simple-uuid、random-32、random-64、random-128、tik)
token-style: uuid
# 是否输出操作日志
is-log: true
# 微信小程序配置
wx:
miniapp:
appid: xxx
secret: xxx
token:
aesKey:
msg-data-format: JSON
config-storage:
type: redis
key-prefix: wa
http-client-type: httpclient
http-proxy-host:
http-proxy-username:
http-proxy-password:
retry-sleep-millis:
max-retry-times:
# 接口文档配置
knife4j:
enable: true
openapi:
title: 微信小程序
description: 微信小程序
email: xxx@qq.com
concat: xxx
url: https://www.baidu.com/
version: v1.0
license: Apache 2.0
license-url: https://stackoverflow.com/
terms-of-service-url: https://stackoverflow.com/
group:
default:
group-name: 默认
api-rule: package
api-rule-resources:
- com.qiangesoft.miniapp.controller
3.3 配置属性类
package com.qiangesoft.miniapp.properties;
import com.qiangesoft.miniapp.enums.HttpClientType;
import com.qiangesoft.miniapp.enums.StorageType;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* 属性配置类
*
* @author qiangesoft
* @since 2024-07-16
*/
@Data
@ConfigurationProperties(prefix = WxMaProperties.PREFIX)
public class WxMaProperties {
public static final String PREFIX = "wx.miniapp";
/**
* 微信小程序的appid.
*/
private String appid;
/**
* 微信小程序的Secret.
*/
private String secret;
/**
* 微信小程序消息服务器配置的token.
*/
private String token;
/**
* 微信小程序消息服务器配置的EncodingAESKey.
*/
private String aesKey;
/**
* 消息格式,XML或者JSON.
*/
private String msgDataFormat;
/**
* 存储策略
*/
private final ConfigStorage configStorage = new ConfigStorage();
/**
* 存储配置类
*/
@Data
public static class ConfigStorage {
/**
* 存储类型.
*/
private StorageType type = StorageType.Memory;
/**
* 指定key前缀.
*/
private String keyPrefix = "wa";
/**
* http客户端类型.
*/
private HttpClientType httpClientType = HttpClientType.HttpClient;
/**
* http代理主机.
*/
private String httpProxyHost;
/**
* http代理端口.
*/
private Integer httpProxyPort;
/**
* http代理用户名.
*/
private String httpProxyUsername;
/**
* http代理密码.
*/
private String httpProxyPassword;
/**
* http 请求重试间隔
* <pre>
* {@link cn.binarywang.wx.miniapp.api.impl.BaseWxMaServiceImpl#setRetrySleepMillis(int)}
* </pre>
*/
private Integer retrySleepMillis = 1000;
/**
* http 请求最大重试次数
* <pre>
* {@link cn.binarywang.wx.miniapp.api.impl.BaseWxMaServiceImpl#setMaxRetryTimes(int)}
* </pre>
*/
private Integer maxRetryTimes = 5;
}
}
3.4 微信相关存储方式配置类
package com.qiangesoft.miniapp.config.ma.store;
import cn.binarywang.wx.miniapp.config.impl.WxMaDefaultConfigImpl;
import cn.hutool.core.util.StrUtil;
import com.qiangesoft.miniapp.properties.WxMaProperties;
/**
* 微信配置抽象
*
* @author qiangesoft
* @since 2024-07-16
*/
public abstract class AbstractWxMaStorageConfiguration {
/**
* 配置
*
* @param config
* @param properties
* @return
*/
protected WxMaDefaultConfigImpl config(WxMaDefaultConfigImpl config, WxMaProperties properties) {
// 基本配置
config.setAppid(StrUtil.trimToNull(properties.getAppid()));
config.setSecret(StrUtil.trimToNull(properties.getSecret()));
config.setToken(StrUtil.trimToNull(properties.getToken()));
config.setAesKey(StrUtil.trimToNull(properties.getAesKey()));
config.setMsgDataFormat(StrUtil.trimToNull(properties.getMsgDataFormat()));
// 代理配置
WxMaProperties.ConfigStorage configStorageProperties = properties.getConfigStorage();
config.setHttpProxyHost(configStorageProperties.getHttpProxyHost());
config.setHttpProxyUsername(configStorageProperties.getHttpProxyUsername());
config.setHttpProxyPassword(configStorageProperties.getHttpProxyPassword());
if (configStorageProperties.getHttpProxyPort() != null) {
config.setHttpProxyPort(configStorageProperties.getHttpProxyPort());
}
int maxRetryTimes = configStorageProperties.getMaxRetryTimes();
if (configStorageProperties.getMaxRetryTimes() < 0) {
maxRetryTimes = 0;
}
int retrySleepMillis = configStorageProperties.getRetrySleepMillis();
if (retrySleepMillis < 0) {
retrySleepMillis = 1000;
}
config.setRetrySleepMillis(retrySleepMillis);
config.setMaxRetryTimes(maxRetryTimes);
return config;
}
}
package com.qiangesoft.miniapp.config.ma.store;
import cn.binarywang.wx.miniapp.config.WxMaConfig;
import cn.binarywang.wx.miniapp.config.impl.WxMaDefaultConfigImpl;
import com.qiangesoft.miniapp.properties.WxMaProperties;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 内存缓存配置
*
* @author qiangesoft
* @since 2024-07-16
*/
@Configuration
@ConditionalOnProperty(prefix = WxMaProperties.PREFIX + ".config-storage", name = "type", matchIfMissing = true, havingValue = "memory")
@RequiredArgsConstructor
public class MemoryWxMaStorageConfiguration extends AbstractWxMaStorageConfiguration {
private final WxMaProperties wxMaProperties;
@Bean
@ConditionalOnMissingBean(WxMaConfig.class)
public WxMaConfig wxMaConfig() {
WxMaDefaultConfigImpl config = new WxMaDefaultConfigImpl();
return this.config(config, wxMaProperties);
}
}
package com.qiangesoft.miniapp.config.ma.store;
import cn.binarywang.wx.miniapp.config.WxMaConfig;
import cn.binarywang.wx.miniapp.config.impl.WxMaRedisBetterConfigImpl;
import com.qiangesoft.miniapp.properties.WxMaProperties;
import lombok.RequiredArgsConstructor;
import me.chanjar.weixin.common.redis.RedisTemplateWxRedisOps;
import me.chanjar.weixin.common.redis.WxRedisOps;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.StringRedisTemplate;
/**
* redis缓存配置
*
* @author qiangesoft
* @since 2024-07-16
*/
@Configuration
@ConditionalOnProperty(prefix = WxMaProperties.PREFIX + ".config-storage", name = "type", havingValue = "redis")
@ConditionalOnClass(StringRedisTemplate.class)
@RequiredArgsConstructor
public class RedisWxMaStorageConfiguration extends AbstractWxMaStorageConfiguration {
private final WxMaProperties properties;
private final ApplicationContext applicationContext;
@Bean
@ConditionalOnMissingBean(WxMaConfig.class)
public WxMaConfig wxMaConfig() {
WxMaRedisBetterConfigImpl config = getWxMaInRedisTemplateConfigStorage();
return this.config(config, properties);
}
private WxMaRedisBetterConfigImpl getWxMaInRedisTemplateConfigStorage() {
StringRedisTemplate redisTemplate = applicationContext.getBean(StringRedisTemplate.class);
WxRedisOps redisOps = new RedisTemplateWxRedisOps(redisTemplate);
return new WxMaRedisBetterConfigImpl(redisOps, properties.getConfigStorage().getKeyPrefix());
}
}
package com.qiangesoft.miniapp.config.ma;
import com.qiangesoft.miniapp.config.ma.store.MemoryWxMaStorageConfiguration;
import com.qiangesoft.miniapp.config.ma.store.RedisWxMaStorageConfiguration;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
/**
* 微信小程序存储策略自动配置
*
* @author qiangesoft
* @since 2024-07-16
*/
@Configuration
@Import({MemoryWxMaStorageConfiguration.class, RedisWxMaStorageConfiguration.class})
public class WxMaStorageAutoConfiguration {
}
3.5 平台服务配置类
package com.qiangesoft.miniapp.config.ma;
import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.api.impl.WxMaServiceHttpClientImpl;
import cn.binarywang.wx.miniapp.api.impl.WxMaServiceImpl;
import cn.binarywang.wx.miniapp.api.impl.WxMaServiceJoddHttpImpl;
import cn.binarywang.wx.miniapp.api.impl.WxMaServiceOkHttpImpl;
import cn.binarywang.wx.miniapp.config.WxMaConfig;
import com.qiangesoft.miniapp.enums.HttpClientType;
import com.qiangesoft.miniapp.properties.WxMaProperties;
import lombok.AllArgsConstructor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 微信小程序平台相关服务自动注册
*
* @author qiangesoft
* @since 2024-07-16
*/
@Configuration
@AllArgsConstructor
public class WxMaServiceAutoConfiguration {
private final WxMaProperties wxMaProperties;
@Bean
@ConditionalOnMissingBean(WxMaService.class)
public WxMaService wxMaService(WxMaConfig wxMaConfig) {
HttpClientType httpClientType = wxMaProperties.getConfigStorage().getHttpClientType();
WxMaService wxMaService;
switch (httpClientType) {
case OkHttp:
wxMaService = new WxMaServiceOkHttpImpl();
break;
case JoddHttp:
wxMaService = new WxMaServiceJoddHttpImpl();
break;
case HttpClient:
wxMaService = new WxMaServiceHttpClientImpl();
break;
default:
wxMaService = new WxMaServiceImpl();
break;
}
wxMaService.setWxMaConfig(wxMaConfig);
return wxMaService;
}
}
package com.qiangesoft.miniapp.config.ma;
import com.qiangesoft.miniapp.properties.WxMaProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
/**
* 自动配置
*
* @author qiangesoft
* @since 2024-07-16
*/
@Configuration
@EnableConfigurationProperties(WxMaProperties.class)
@Import({WxMaStorageAutoConfiguration.class, WxMaServiceAutoConfiguration.class})
public class WxMaAutoConfiguration {
}
3.6 其他配置
package com.qiangesoft.miniapp.config;
import cn.dev33.satoken.stp.StpUtil;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
/**
* 自定义sql字段填充器,自动填充创建修改相关字段
*
* @author qiangesoft
* @date 2024-04-11
*/
@Component
public class CustomMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
setFieldValByName("createUser", StpUtil.getLoginIdAsLong(), metaObject);
setFieldValByName("createTime", LocalDateTime.now(), metaObject);
}
@Override
public void updateFill(MetaObject metaObject) {
setFieldValByName("updateUser", StpUtil.getLoginIdAsLong(), metaObject);
setFieldValByName("updateTime", LocalDateTime.now(), metaObject);
}
}
package com.qiangesoft.miniapp.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* mybatis-plus配置
*
* @author qiangesoft
* @date 2024-04-11
*/
@Configuration
public class MybatisPlusConfig {
/**
* 分页插件
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
3.7 常量
package com.qiangesoft.miniapp.enums;
/**
* httpclient类型
*
* @author qiangesoft
* @since 2024-07-16
*/
public enum HttpClientType {
/**
* HttpClient.
*/
HttpClient,
/**
* OkHttp.
*/
OkHttp,
/**
* JoddHttp.
*/
JoddHttp,
}
package com.qiangesoft.miniapp.enums;
/**
* storage类型
*
* @author qiangesoft
* @since 2024-07-16
*/
public enum StorageType {
/**
* 内存.
*/
Memory,
/**
* redis.
*/
Redis
}
3.8 启动类
package com.qiangesoft.miniapp;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* 启动类
*
* @author qiangesoft
* @since 2024-07-16
*/
@MapperScan("com.qiangesoft.miniapp.mapper")
@SpringBootApplication
public class WxMaApplication {
public static void main(String[] args) {
SpringApplication.run(WxMaApplication.class, args);
}
}