最近在完成一个微信小程序项目,即将上线
欢迎star
Github–ShareNotes
写小程序接口遇到的具体情况
在最早的时候我帮过朋友写过一个花店卖花的程序。
最开始的时候大多数都是通过
微信官方文档API
找到api。然后RestTemplate来访问url。
RestTemplate是Spring提供的用于访问Rest服务的客户端。
@Autowird
private RestTemplate restTemplate;
WxConfig wxConfig= restTemplate.getForObject("https://api.weixin.qq.com/xxxxx/" , WxConfig.class);
十分的烦琐
Github-Binary Wang
开始操作
com.github.binarywang
weixin-java-miniapp
3.3.0
com.github.binarywang
weixin-java-pay
3.3.0
jwt的依赖
com.auth0
java-jwt
3.4.1
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(63) NOT NULL COMMENT '用户名称',
`password` varchar(63) NOT NULL DEFAULT '' COMMENT '用户密码',
`gender` tinyint(3) NOT NULL DEFAULT '0' COMMENT '性别:0 未知, 1男, 1 女',
`birthday` date DEFAULT NULL COMMENT '生日',
`last_login_time` datetime DEFAULT NULL COMMENT '最近一次登录时间',
`last_login_ip` varchar(63) NOT NULL DEFAULT '' COMMENT '最近一次登录IP地址',
`nickname` varchar(63) NOT NULL DEFAULT '' COMMENT '用户昵称或网络名称',
`mobile` varchar(20) NOT NULL DEFAULT '' COMMENT '用户手机号码',
`avatar` varchar(255) NOT NULL DEFAULT '' COMMENT '用户头像图片',
`weixin_openid` varchar(63) NOT NULL DEFAULT '' COMMENT '微信登录openid',
`session_key` varchar(100) NOT NULL DEFAULT '' COMMENT '微信登录会话KEY',
`status` tinyint(3) NOT NULL DEFAULT '0' COMMENT '0 可用, 1 禁用, 2 注销',
`add_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`deleted` tinyint(1) DEFAULT '0' COMMENT '逻辑删除',
PRIMARY KEY (`id`),
UNIQUE KEY `user_name` (`username`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
本质是 weixin_openid,session_key。
因为登录不需要账户密码。是直接登录。所以如果第一次登录创建用户的时候username和password可以设置为 weixin_openid,或者任意值。
如果weixin-java-miniapp依赖已经导入完成就可以开始配置你的appid和appsecret,其在到微信开发者平台里头
wx:
app-id: xxxxx
app-secret: xxxxx
mch-id: xxxxxxxx
mch-key: xxxxxx
notify-url: http://www.example.com/wx/order/pay-notify
# 商户证书文件路径
# 请参考“商户证书”一节 https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=4_3
key-path: xxxxx
@Configuration
@ConfigurationProperties(prefix = "wx")
public class WxProperties {
private String appId;
private String appSecret;
private String mchId;
private String mchKey;
private String notifyUrl;
private String keyPath;
public String getNotifyUrl() {
return notifyUrl;
}
public void setNotifyUrl(String notifyUrl) {
this.notifyUrl = notifyUrl;
}
public String getMchKey() {
return mchKey;
}
public void setMchKey(String mchKey) {
this.mchKey = mchKey;
}
public String getAppId() {
return this.appId;
}
public void setAppId(String appId) {
}
public String getAppSecret() {
return appSecret;
}
}
}
this.keyPath = keyPath;
public void setKeyPath(String keyPath) {
}
return keyPath;
public String getKeyPath() {
}
this.mchId = mchId;
public void setMchId(String mchId) {
}
return mchId;
public String getMchId() {
}
this.appSecret = appSecret;
public void setAppSecret(String appSecret) {
@Configuration
public class WxConfig {
@Autowired
private WxProperties properties;
@Bean
public WxMaConfig wxMaConfig() {
WxMaInMemoryConfig config = new WxMaInMemoryConfig();
config.setAppid(properties.getAppId());
config.setSecret(properties.getAppSecret());
return config;
}
@Bean
public WxMaService wxMaService(WxMaConfig maConfig) {
WxMaService service = new WxMaServiceImpl();
service.setWxMaConfig(maConfig);
return service;
}
@Bean
public WxPayConfig wxPayConfig() {
WxPayConfig payConfig = new WxPayConfig();
payConfig.setAppId(properties.getAppId());
payConfig.setMchId(properties.getMchId());
payConfig.setMchKey(properties.getMchKey());
payConfig.setNotifyUrl(properties.getNotifyUrl());
payConfig.setKeyPath(properties.getKeyPath());
payConfig.setTradeType("JSAPI");
payConfig.setSignType("MD5");
return payConfig;
}
@Bean
public WxPayService wxPayService(WxPayConfig payConfig) {
WxPayService wxPayService = new WxPayServiceImpl();
wxPayService.setConfig(payConfig);
return wxPayService;
}
}
所有关于微信小程序配置都在这里了。
然后我们可以使用他封装好的wxMaService了。
User.java
@Data
public class User {
private Integer id;
private String username;
private String password;
private Byte gender;
private Date birthday;
private Date lastLoginTime;
private String lastLoginIp;
private String nickname;
private String mobile;
private String avatar;
private String weixinOpenid;
private String sessionKey;
private Byte status;
private Date addTime;
private Date updateTime;
private Boolean deleted;
}
WxLoginInfo.java
public class WxLoginInfo {
private String code;
private UserDto userInfo;
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public UserDto getUserInfo() {
return userInfo;
}
public void setUserInfo(UserDto userInfo) {
this.userInfo = userInfo;
}
}
UserDto.java
@Data
public class UserDto {
private String nickName;
private String avatarUrl;
private String country;
private String province;
private String city;
private String language;
private Byte gender;
UserService.java
需要放这两个方法
public interface UserService {
//根据openId查询
public User queryByOid(String openId);
//加入user表
public void add(User user);
实现
UserServiceImpl.java
@Override
public User queryByOid(String openId) {
//这里用了Example
UserExample example = new UserExample();
example.or().andWeixinOpenidEqualTo(openId).andDeletedEqualTo(false);
return userMapper.selectOneByExample(example);
}
@Override
public void add(User user) {
user.setAddTime(new Date());
user.setUpdateTime(new Date());
userMapper.insertSelective(user);
}
WxAuthController.java
@Slf4j
@RestController
@RequestMapping("/wx/auth")
@Validated
public class WxAuthController {
@Autowired
private UserService userService;
//这里是之前添加依赖里面的方法
@Autowired
private WxMaService wxService;
@PostMapping("login_by_weixin")
public Object loginByWeixin(@RequestBody WxLoginInfo wxLoginInfo, HttpServletRequest request) {
String code = wxLoginInfo.getCode();
UserDto userDto = wxLoginInfo.getUserInfo();
if (code == null || userDto == null) {
return ResponseUtil.badArgument();
}
String sessionKey = null;
String openId = null;
try {
WxMaJscode2SessionResult result = this.wxService.getUserService().getSessionInfo(code);
sessionKey = result.getSessionKey();
openId = result.getOpenid();
} catch (Exception e) {
e.printStackTrace();
}
if (sessionKey == null || openId == null) {
return ResponseUtil.fail();
}
User user = userService.queryByOid(openId);
if (user == null) {
user = new User();
user.setUsername(openId);
user.setPassword(openId);
user.setWeixinOpenid(openId);
user.setAvatar(userDto.getAvatarUrl());
user.setNickname(userDto.getNickName());
user.setGender(userDto.getGender());
user.setStatus((byte) 0);
user.setLastLoginTime(new Date());
user.setLastLoginIp(IpUtil.getIpAddr(request));
user.setSessionKey(sessionKey);
userService.add(user);
} else {
user.setLastLoginTime(new Date());
user.setLastLoginIp(IpUtil.getIpAddr(request));
user.setSessionKey(sessionKey);
if (userService.updateById(user) == 0) {
return ResponseUtil.updatedDataFailed();
}
}
// token
String token = UserTokenManager.generateToken(user.getId());
Map
这里的ResponseUtil.ok(result)是我自己的返回值封装类。
举个例子
public static Object ok(Object data) {
Map obj = new HashMap();
obj.put("errno", 0);
obj.put("errmsg", "成功");
obj.put("data", data);
return obj;
}
接上面的 UserTokenManager
JwtHelper----> UserTokenManager
JwtHelper.java
public class JwtHelper {
// 秘钥
static final String SECRET = "YOUR-SECRET-TOKEN";
// 签名是有谁生成
static final String ISSUSER = "SECRET";
// 签名的主题
static final String SUBJECT = "this is you token";
// 签名的观众
static final String AUDIENCE = "MINIAPP";
public String createToken(Integer userId){
try {
Algorithm algorithm = Algorithm.HMAC256(SECRET);
Map map = new HashMap();
Date nowDate = new Date();
// 过期时间:7天2小时
Date expireDate = getAfterDate(nowDate,0,0,7,2,0,0);
map.put("alg", "HS256");
map.put("typ", "JWT");
String token = JWT.create()
// 设置头部信息 Header
.withHeader(map)
// 设置 载荷 Payload
.withClaim("userId", userId)
.withIssuer(ISSUSER)
.withSubject(SUBJECT)
.withAudience(AUDIENCE)
// 生成签名的时间
.withIssuedAt(nowDate)
// 签名过期的时间
.withExpiresAt(expireDate)
// 签名 Signature
.sign(algorithm);
return token;
} catch (JWTCreationException exception){
exception.printStackTrace();
}
return null;
}
public Integer verifyTokenAndGetUserId(String token) {
try {
Algorithm algorithm = Algorithm.HMAC256(SECRET);
JWTVerifier verifier = JWT.require(algorithm)
.withIssuer(ISSUSER)
.build();
DecodedJWT jwt = verifier.verify(token);
Map claims = jwt.getClaims();
Claim claim = claims.get("userId");
return claim.asInt();
} catch (JWTVerificationException exception){
// exception.printStackTrace();
}
return 0;
}
public Date getAfterDate(Date date, int year, int month, int day, int hour, int minute, int second){
if(date == null){
date = new Date();
}
Calendar cal = new GregorianCalendar();
cal.setTime(date);
if(year != 0){
cal.add(Calendar.YEAR, year);
}
if(month != 0){
cal.add(Calendar.MONTH, month);
}
if(day != 0){
cal.add(Calendar.DATE, day);
}
if(hour != 0){
cal.add(Calendar.HOUR_OF_DAY, hour);
}
if(minute != 0){
cal.add(Calendar.MINUTE, minute);
}
if(second != 0){
cal.add(Calendar.SECOND, second);
}
return cal.getTime();
}
}
UserTokenManager.java
public static String generateToken(Integer id) {
JwtHelper jwtHelper = new JwtHelper();
return jwtHelper.createToken(id);
}
public static Integer getUserId(String token) {
JwtHelper jwtHelper = new JwtHelper();
Integer userId = jwtHelper.verifyTokenAndGetUserId(token);
if(userId == null || userId == 0){
return null;
}
return userId;
}
user.js
function loginByWeixin(userInfo) {
return new Promise(function (resolve, reject) {
return login().then((res) => {
//这里的api.AuthLoginByWeixin 为 localhost:8080/wx/auth/login_by_weixin
util.request(api.AuthLoginByWeixin, {
code: res.code,
userInfo: userInfo
}, 'POST').then(res => {
if (res.errno === 0) {
//存储用户信息
wx.setStorageSync('userInfo', res.data.userInfo);
wx.setStorageSync('token', res.data.token);
resolve(res);
} else {
reject(res);
}
}).catch((err) => {
reject(err);
});
}).catch((err) => {
reject(err);
})
});
}
utils.js
function request(url, data = {}, method = "GET") {
return new Promise(function (resolve, reject) {
wx.request({
url: url,
data: data,
method: method,
header: {
'Content-Type': 'application/json',
//刚刚你设置的token
'YOUR-SECRET-TOKEN': wx.getStorageSync('token')
},
success: function (res) {
if (res.statusCode == 200) {
if (res.data.errno == 501) {
// 清除登录相关内容
try {
wx.removeStorageSync('userInfo');
wx.removeStorageSync('token');
} catch (e) {
// Do something when catch error
}
// 切换到登录页面
wx.navigateTo({
url: '/pages/ucenter/index/index'
});
} else {
resolve(res.data);
}
} else {
reject(res.errMsg);
}
},
fail: function (err) {
reject(err)
}
})
});
}
小程序页面js的使用
user.loginByWeixin(e.detail.userInfo).then(res => {
app.globalData.hasLogin = true;
this.onShow();
}).catch((err) => {
app.globalData.hasLogin = false;
util.showErrorToast('微信登录失败');
});
关于登录就到这里了。可以打开我的GitHub源码其分别对应
Github–ShareNotes
实体类和mapper还有sql
share-Notes-db----->sql文件夹
share-Notes-db----->src–>main–>java–>cn.sharenotes.db---->domain/mapper/model
wxconfig配置
share-Notes-core----->src–>main–>java–>cn.sharenotes.core---->config
share-Notes-core----->src–>main–>java–>cn.sharenotes.core---->utils
share-Notes-core----->src–>main–>resources---->wxconfig.properties(我这里不是yml)
controller使用
share-Notes-wx-api----->src–>main–>java–>cn.sharenotes.wxapi---->web
share-Notes-wx-api----->src–>main–>java–>cn.sharenotes.wxapi---->service
我们回到wxconfig中
重新添加上wxmaSecCheckService
@Slf4j
@Configuration
@PropertySource(value = "classpath:wxconf.properties")
public class WxConfig {
@Value("${APP_ID}")
private String appId;
@Value("${APP_SERCET}")
private String appSecret;
@Bean
public WxMaConfig wxMaConfig() {
WxMaInMemoryConfig config = new WxMaInMemoryConfig();
config.setAppid(appId);
config.setSecret(appSecret);
log.info("id"+appId);
log.info("key"+appSecret);
return config;
}
@Bean
public WxMaService wxMaService(WxMaConfig maConfig) {
WxMaService service = new WxMaServiceImpl();
service.setWxMaConfig(maConfig);
return service;
}
@Bean
public WxMaSecCheckService wxMaSecCheckService(){
WxMaSecCheckService wxMaSecCheckService = new WxMaSecCheckServiceImpl(wxMaService(wxMaConfig()));
return wxMaSecCheckService;
}
@Bean
public WxPayConfig wxPayConfig() {
WxPayConfig payConfig = new WxPayConfig();
payConfig.setAppId(appId);
payConfig.setTradeType("JSAPI");
payConfig.setSignType("MD5");
return payConfig;
}
@Bean
public WxPayService wxPayService(WxPayConfig payConfig) {
WxPayService wxPayService = new WxPayServiceImpl();
wxPayService.setConfig(payConfig);
return wxPayService;
}
}
文字过滤
@Autowired
private WxMaSecCheckService wxMaSecCheckService;
if(!wxMaSecCheckService.checkMessage(JacksonUtil.parseString(body, "name"))){
ResponseUtil.fail(500,"违法违规标题");
}
图片
wxMaSecCheckService.checkImage(f)
他的方法都是返回布尔值。
https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/sec-check/security.imgSecCheck.html
这里的本质是使用微信开发文档中的api
POST https://api.weixin.qq.com/wxa/img_sec_check?access_token=ACCESS_TOKEN
通过post请求这段api返回是否是内容安全。
官方文档很详细了。返回errcode和errMsg
通过为0失败为87014
Post的body中放图片。
不过我们看一下整句api,后面有一个accessToken。
这个accesstoken怎么获取呢。
回到官方文档
获取小程序全局唯一后台接口调用凭据(access_token)。调调用绝大多数后台接口时都需使用 access_token,开发者需要进行妥善保存。
api为
GET https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET
这里需要我们的appid和appsecret。
请求后可以过去accesstoken。但是请求的token只会支持2个小时,后他将重新生成。2个小时后得再次请求。
微信官方api中关于服务端的有趣api很多,可以查阅。虽然有请求次数限制。但是已经足够了