vue+springboot微信公众网页项目开发及腾讯云服务器部署
前言
本项目前端采用vue-cli2脚手架生成,vscode编辑;后端idea编辑。微信公众网页开发,只需在公众平台配置回调域名等相关操作(后面会有介绍),其他与正常开发相同。
本系列文章只提供微信公众号开发到上线环境配置的调通,开发内容略少,感谢网上各位博主的分享,让我能够顺利完成项目的开发与文档的编写。
文章目录
(一)前期开发准备
-
vue项目创建
npm install -g vue-cli vue init webpack
-
springboot项目创建
使用idea快速创建。教程见:点击查看
-
微信公众平台测试号申请
在微信公众平台申请测试号。申请成功后,在跳转页面中填写以下信息:
两个域名进行修改。第二章图片点击网页账号栏目右侧的修改按钮进行域名修改。完成后就可以进行网页授权和js-sdk的使用了。
内网透射(测试域名):点击查看
(二)vue前端项目开发
-
host检查
可以在build目录下的webpack.dev.conf.js文件,devServer下添加disableHostCheck: true,跳过检查
在config目录下修改index.js文件的host,这个默认是localhost,可修改
npm install less-loader@5.0.0 less@3.9.0 --save-dev
只有安装了之后,才能选择样式为less哦。要指定版本,不然会因为node版本过高或者webpack版本问题出错哦。
--save-dev 指把依赖安装在开发环境中,项目打包后不会添加进去。
-
调用后端api
为了方便 封装请求
request.js文件 import axios from 'axios' import { Toast } from 'vant' // create an axios instance const service = axios.create({ baseURL: 'https://ip:5000', // withCredentials: true, // send cookies when cross-domain requests timeout: 5000 // request timeout }) // // request interceptor service.interceptors.request.use( config => { // do something before request is sent return config }, error => { // do something with request error console.log(error) // for debug return Promise.reject(error) } ) // // response interceptor service.interceptors.response.use( /** * If you want to get http information such as headers or status * Please return response => response */ /** * Determine the request status by custom code * Here is just an example * You can also judge the status by HTTP Status Code */ response => { const res = response.data // if the custom code is not 20000, it is judged as an error. // if (res.code !== 200) { // Toast({ // message: res.message || 'Error', // duration: 1500 // }) // return Promise.reject(new Error(res.message || 'Error')) // } else { // return res // } return res }, error => { console.log('err' + error) // for debug Toast({ message: 'Error', duration: 1500 }) return Promise.reject(error) } ) export default service
-
小tips
vue2 assets访问: require(‘url’)
vant组件样式修改(样例): .van-field /deep/ .van-field__label{}
(三)springboot后端项目开发
- 为了避免和前端项目启动端口冲突,更改后端端口。
application.yml文件(更改properties后缀名)
······
server:
port: 5000
······
-
为了解决跨域问题,后端配置
package com.fgs.common.config; import org.apache.commons.lang.StringUtils; import org.springframework.context.annotation.Configuration; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; @Configuration public class CorsFilter implements Filter { @Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletResponse response = (HttpServletResponse) res; HttpServletRequest request = (HttpServletRequest) req; // 不使用*,自动适配跨域域名,避免携带Cookie时失效 String origin = request.getHeader("Origin"); if(StringUtils.isNotBlank(origin)) { response.setHeader("Access-Control-Allow-Origin", origin); } // 自适应所有自定义头 String headers = request.getHeader("Access-Control-Request-Headers"); if(StringUtils.isNotBlank(headers)) { response.setHeader("Access-Control-Allow-Headers", headers); response.setHeader("Access-Control-Expose-Headers", headers); } // 允许跨域的请求方法类型 response.setHeader("Access-Control-Allow-Methods", "*"); // 预检命令(OPTIONS)缓存时间,单位:秒 response.setHeader("Access-Control-Max-Age", "3600"); // 明确许可客户端发送Cookie,不允许删除字段即可 response.setHeader("Access-Control-Allow-Credentials", "true"); chain.doFilter(request, response); } @Override public void init(FilterConfig filterConfig) { } @Override public void destroy() { } }
(四)微信公众号网页授权获取openid和用户信息
微信js-sdk引入(npm安装):点击查看安装方式 本项目没有用到js-sdk 仅在此说明如何安装
建立三个文件。注意:使用resttemplate来调用外部api需要在启动项进行一下配置
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
package com.fgs.application.api;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.fgs.domain.entity.Oauth2Token;
import com.fgs.domain.entity.WxUserDO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;
@RestController
@Slf4j
public class WxApi {
@Autowired
private RestTemplate restTemplate;
private static final String APP_ID = "";
private static final String APP_SECRET = "";
private static final String BACK_URL = "回调地址";
// 前端获取返回的url来访问获取code 前端:window.location.href = url
@RequestMapping("/wxLoginInit")
public String loginInit(HttpServletRequest request, HttpServletResponse response) throws IOException {
String url = "https://open.weixin.qq.com/connect/oauth2/authorize?appid="
+ APP_ID
+ "&redirect_uri="
+ URLEncoder.encode(BACK_URL, "UTF-8")
+ "&response_type=code"
+ "&scope=snsapi_base"
+ "&state=STATE#wechat_redirect" ;
return url;
}
// 获取前端得到code获取信息
@RequestMapping("/wxLogin")
public String wxLogin(@RequestParam("code") String code) {
log.info("code: " + code);
String openId = "";
Oauth2Token oauth2Token = getOauth2Token(APP_ID, APP_SECRET, code);
String accessToken = oauth2Token.getAccessToken();
// 用户标识
openId = oauth2Token.getOpenId();
// 获取用户信息
WxUserDO wxUserInfo = getWxUserInfo(accessToken, openId);
openId = wxUserInfo.getOpenId();
// 具体业务 start
// 具体有什么业务需求自己实现,我这里只把 Oauth2Token 和 WxUserDO 的信息输出
// ...
log.info(oauth2Token.toString());
log.info("用户标识openId:" + openId);
log.info(wxUserInfo.toString());
return openId;
}
private WxUserDO getWxUserInfo(String accessToken, String openId) {
log.info("获取用户信息 开始...");
log.info("网页授权接口访问凭证AccessToken:" + accessToken);
log.info("用户标识openId:" + openId);
WxUserDO wxUserInfo = null;
// 拼接请求地址
String requestUrl = "https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN";
requestUrl = requestUrl.replace("ACCESS_TOKEN", accessToken);
requestUrl = requestUrl.replace("OPENID", openId);
log.info("拼接请求地址:" + requestUrl);
// 通过网页授权获取用户信息
ResponseEntity<String> responseEntity = restTemplate.getForEntity(requestUrl, String.class);
String body = responseEntity.getBody();
JSONObject jsonObject = JSON.parseObject(body);
if (null != jsonObject) {
try {
wxUserInfo = new WxUserDO();
wxUserInfo.setOpenId(jsonObject.getString("openid"));
wxUserInfo.setNickname(jsonObject.getString("nickname"));
wxUserInfo.setSex(jsonObject.getInteger("sex"));
wxUserInfo.setHeadimgurl(jsonObject.getString("headimgurl"));
}catch (Exception e) {
wxUserInfo = null;
int errorCode = jsonObject.getInteger("errcode");
String errorMsg = jsonObject.getString("errmsg");
log.error("获取用户信息失败 errcode:{} errmsg:{}",errorCode,errorMsg);
}
}
log.info("获取用户信息 结束...");
return wxUserInfo;
}
private Oauth2Token getOauth2Token(String appId, String appSecret, String code) {
log.info("获取网页授权凭证 开始...");
Oauth2Token auth = null;
//拼接请求地址
String requestUrl = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code";
requestUrl = requestUrl.replace("APPID", appId);
requestUrl = requestUrl.replace("SECRET", appSecret);
requestUrl = requestUrl.replace("CODE", code);
log.info("拼接后的请求地址为:" + requestUrl);
//获取网页授权凭证
ResponseEntity<String> responseEntity = restTemplate.getForEntity(requestUrl, String.class);
String body = responseEntity.getBody();
JSONObject jsonObject = JSON.parseObject (body);
if(null != jsonObject) {
try {
auth = new Oauth2Token();
auth.setAccessToken(jsonObject.getString("access_token"));
auth.setExpiresIn(jsonObject.getInteger("expires_in"));
auth.setRefreshToken("refresh_token");
auth.setOpenId("openid");
auth.setScope("scope");
}catch(Exception e) {
auth = null;
int errorCode = jsonObject.getInteger("errcode");
String errorMsg = jsonObject.getString("errmsg");
log.error("获取网页授权凭证失败 errcode:{} errmsg:{}",errorCode,errorMsg);
}
}
log.info("获取网页授权凭证 结束...");
return auth;
}
}
package com.fgs.domain.entity;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Oauth2Token {
private String accessToken; //网页授权接口调用凭证
private int expiresIn; //凭证有效时长
private String refreshToken; //用于刷新凭证
private String openId; //用户标识
private String scope; //用户授权作用域
@Override
public String toString() {
return "Oauth2Token [accessToken=" + accessToken + ", expiresIn=" + expiresIn + ", refreshToken=" + refreshToken
+ ", openId=" + openId + ", scope=" + scope + "]";
}
}
package com.fgs.domain.entity;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.List;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class WxUserDO implements Serializable {
private static final long serialVersionUID = -8881505873506562352L;
private Integer id; //主键 id
private String openId; //用户主键
private String nickname; //用户昵称
private Integer sex; //性别(1:男,2:女,0:未知)
private String headimgurl; //用户头像链接
//get 和 set 方法
@Override
public String toString() {
return "WxUserDO [id=" + id + ", openId=" + openId + ", nickname=" + nickname + ", sex=" + sex + ", country="
+ ", province=" + ", city=" + ", headimgurl=" + headimgurl + ", unionid="
+ ", privilegeList=" + "]";
}
}
(五)腾讯云服务器部署(windows 2012 server)
-
mysql安装
-
jdk安装
-
nginx安装以及证书nginx方式部署
nginx下载及使用 解压后在当前目录下start nginx 即可启动,详情见链接页面中document
-
vue项目打包
npm run build
-
springboot项目打包
可在idea下增加启动配置
或直接在项目根目录下cmd命令:mvn clean package
-
启动服务器上项目
将打包后的dist文件夹下的文件移动到nginx文件夹中html文件夹中,springboot打包的jar包随意放。
# 进入nginx安装文件夹 start nginx # 进入jar包所在文件夹 java -jar jar包
启动成功后就可以进行访问了
(六)springboot https访问
因为nginx代理了vue访问,在vue访问后端api时 可能也需要https请求访问。因此配置证书,这里的证书去腾讯云服务器里下载。压缩包里tomcat文件夹,然后将.jks文件放在与application.yml文件同级目录下
application.yml
server:
port: 5000
ssl:
key-store: classpath:域名.jks
key-store-type: JKS
key-store-password: 密码
key-password: 密码
http转https,这里如果是用nginx代理vue前端项目就用不到。如果是将vue项目放在springboot的static下,会用到
/**
* http重定向到https
* @return
*/
@Bean
public TomcatServletWebServerFactory servletContainer() {
TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory() {
@Override
protected void postProcessContext(Context context) {
SecurityConstraint constraint = new SecurityConstraint();
constraint.setUserConstraint("CONFIDENTIAL");
SecurityCollection collection = new SecurityCollection();
collection.addPattern("/*");
constraint.addCollection(collection);
context.addConstraint(constraint);
}
};
tomcat.addAdditionalTomcatConnectors(httpConnector());
return tomcat;
}
@Bean
public Connector httpConnector() {
Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
connector.setScheme("http");
//Connector监听的http的默认端口号
connector.setPort(5000);
connector.setSecure(false);
//监听到http的端口号后转向到的https的端口号,也就是项目配置的port
connector.setRedirectPort(5000);
return connector;
}
tomcat.addAdditionalTomcatConnectors(httpConnector());
return tomcat;
}
@Bean
public Connector httpConnector() {
Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
connector.setScheme("http");
//Connector监听的http的默认端口号
connector.setPort(5000);
connector.setSecure(false);
//监听到http的端口号后转向到的https的端口号,也就是项目配置的port
connector.setRedirectPort(5000);
return connector;
}