vue3 pinia vite
tp6 easywechat
在router.js中
// 定义一个全局前置守卫,在所有路由变化之前执行
router.beforeEach(async (to, from, next) => {
// 使用pinia或Vuex的store来获取用户信息和管理员信息
const userStore = useUserStore()
// 检查目标路由是否有requiresAuth元信息,即是否需要认证
if (to.matched.some(record => record.meta.requiresAuth)) {
// 检查是否是在微信浏览器中打开
if (isWeChatBrowser()) {
// 如果用户尚未登录(id为null或undefined)
if (userStore.id === null || userStore.id === undefined) {
// 对回调页URL进行编码,防止特殊字符引起问题
const url = encodeURIComponent('回调页') // '回调页'应该替换为实际的回调地址
const appid = 'appid' // 微信公众号的appid
const response_type = 'code' // 授权类型
const scope = 'snsapi_userinfo' // 获取用户的基本信息权限
// 函数用来重定向到微信授权页面
const wxlogin = () => {
// 通过window.location.href重定向到微信授权链接
window.location.href = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appid}&redirect_uri=${url}&response_type=${response_type}&scope=${scope}&state=STATE#wechat_redirect`
}
// 调用微信登录函数
return wxlogin()
} else {
// 如果用户已登录,则继续导航到目标路由
next()
}
} else {
// 如果不是在微信浏览器中打开,则提示用户
alert('请在微信中打开使用完整服务')
}
} else {
// 如果目标路由不需要认证,则直接放行
next()
}
})
回调页js
const callback = () => {
const route = useRoute();
const router = useRouter();
const code = ref(route.query.code); // 获取 URL 中的 code 查询参数
if (code.value !== undefined) {
axios.post('url', {
code: code.value
}).then(res => {
数据处理,我这里是用pinia记录了用户状态和一些信息
if () {
} else {
}
}).catch(e => {
})
}
}
onMounted(() => {
document.title = '登录中...'
callback()
})
TP6后端使用了easywechat,这个库可以自行百度
user/WxLogin
public function index(Request $request)
{
$code = $request->post('code');
$config = [
// 微信AppID和AppSecret等配置
'app_id' => 'appid',
'secret' => 'appsecret',
'oauth' => [
'scopes' => ['snsapi_userinfo'],
'callback' => ' ',
],
// 其他配置...
];
// 使用code换取用户信息
$app = Factory::officialAccount($config);
$oauth = $app->oauth;
try {
try {
$user = $oauth->userFromCode($code)->toArray();
$openid=$user['id'];
//微信每天accesstoken有次数限制,定义一个cacheKey 之后用catch存储一下
$cacheKey = $openid.'wechat_access_token';
Cache($cacheKey,$user['access_token'], $result['expires_in'] - 60);
//之后处理业务即可
} catch (GuzzleException|AuthorizeFailedException $e) {
return Response::create(['msg'=>$e->getMessage()], 'json');
}
} catch (\Exception $e) {
// 错误处理
return Response::create(['msg'=>$e->getMessage()], 'json');
}
}
微信支付JS
let url = '';
const href = window.location.href;
url = href.split('#')[0];
ax.post('/user/WxPay', {
url: url,
id: userStore.id,
price: price.value,
}).then((res) => {
wx.config({
debug: false,
appId: 'appid',
timestamp: res.data.timestamp,
nonceStr: res.data.nonce_str,
signature: res.data.signature,
jsApiList: ['chooseWXPay', 'checkJsApi'] // 必填,需要使用的JS接口列表
})
wx.ready(() => {
wx.checkJsApi({
jsApiList: ['chooseWXPay'],
success: function (res) {
},
fail: function (res) {
}
})
wx.chooseWXPay({
appId: 'appid',
timestamp: res.data.timestamp, // 支付签名时间戳,注意微信jssdk中的所有使用timestamp字段均为小写。但最新版的支付后台生成签名使用的timeStamp字段名需大写其中的S字符
nonceStr: res.data.nonce_str, // 支付签名随机串,不长于 32 位
package: res.data.package, // 统一支付接口返回的prepay_id参数值,提交格式如:prepay_id=\*\*\*)
signType: 'MD5', // 签名方式,默认为'SHA1',使用新版支付需传入'MD5'
paySign: res.data.paySign, // 支付签名
success: function (payRes) {
ax.post('/user/Index/CreateOrder', {
id: userStore.user,
type: type,
price: price.value,
_id: id,
remake: remake.value,
}, {
headers: {
Token: userStore.token
}
}).then((res) => {
if (res.data.code === 200) {
alert(res.data.msg)
} else {
alert(res.data.msg)
}
})
},
cancel: function (payRes) {
alert.error('取消支付')
},
fail: function (payRes) {
alert.error('支付失败')
}
})
})
})
wxpay.php
public function index(Request $request): Response
{
$data=$request->only(['price','id','url']);
$config = [
'app_id' => 'appid',
'mch_id' => '商户',
'key' => 'apiv2密钥', // API v2 密钥 (注意: 是v2密钥 是v2密钥 是v2密钥)
'cert_path' => dirname(__DIR__, 3).'/apiclient_cert.pem', // XXX: 绝对路径!!!!
'key_path' => dirname(__DIR__, 3).'/apiclient_key.pem', // XXX: 绝对路径!!!!
'notify_url' => '', // 你也可以在下单时单独设置来想覆盖它
];
$app = Factory::payment($config);
try {
$result = $app->order->unify([
'body' => '在线支付',
'out_trade_no' => $this->generateOrderNo(),
'total_fee' => $data['price']*100,
'trade_type' => 'JSAPI', // 请对应换成你的支付方式对应的值类型
'openid' => $data['id'],
]);
if (isset($result['prepay_id'])){
$prepayId = $result['prepay_id'];
$package = 'prepay_id=' . $prepayId;
$accessToken = $this->getAccessToken($data['id']);
$ticket = $this->getJsapiTicket($data['id'],$accessToken);
$nonceStr = $this->createNonceStr();
$timestamp = time();
$paySign=$this->paySign($nonceStr,$package,$timestamp);
$signature = $this->createSignature($nonceStr, $timestamp, $data['url'], $ticket);
return Response::create([
$result,
'url'=>$data['url'],
'signature'=>$signature,
'paySign'=>$paySign,
'nonce_str' => $nonceStr,
'prepay_id' => $prepayId,
'package' => 'prepay_id=' . $prepayId,
'timestamp' => $timestamp,
'sign_type' => 'MD5',
], 'json');
}
return Response::create(['msg'=>$result,'code'=>200], 'json');
} catch (InvalidArgumentException|InvalidConfigException|GuzzleException $e) {
return Response::create(['msg'=>$e->getMessage(),'code'=>201], 'json');
}
}
function generateOrderNo(): string
{
// 获取当前时间的时间戳(精确到毫秒)
$timestamp = date('YmdHis') . substr(str_replace(".", "", microtime()), 0, 3);
// 生成四位随机数
$randomPart = str_pad(mt_rand(1, 9999), 4, "0", STR_PAD_LEFT);
// 拼接时间戳和随机数
return $timestamp . $randomPart;
}
/**
* @throws \Exception
*/
private function getAccessToken($id)
{
$appId='appid';
$appSecret ='appSecret ';
$cacheKey = $id.'wechat_access_token';
$accessToken=Cache($cacheKey);
if (empty($accessToken)){
$url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={$appId}&secret={$appSecret}";
$response = file_get_contents($url);
$result = json_decode($response, true);
if (!isset($result['access_token'])) {
throw new \Exception('获取accesstoken失败'. $result['errmsg']);
}
$accessToken = $result['access_token'];
Cache($cacheKey, $accessToken, $result['expires_in'] - 60);
}
return $accessToken;
}
public function AccessToken(Request $request): Response
{
$id = $request->post('id');
$appId='appId';
$appSecret ='appSecret ';
$cacheKey = $id.'wechat_access_token';
$accessToken=Cache($cacheKey);
if (empty($accessToken)){
$url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={$appId}&secret={$appSecret}";
$response = file_get_contents($url);
$result = json_decode($response, true);
if (!isset($result['access_token'])) {
throw new \Exception('获取accesstoken失败');
}
$accessToken = $result['access_token'];
Cache($cacheKey, $accessToken, $result['expires_in'] - 60);
}
return Response::create(['ac'=>$accessToken,'code'=>200], 'json');
}
private function getJsapiTicket($id,$accessToken)
{
$cacheKey = $id.'wechat_jsapi_ticket';
$ticket =Cache($cacheKey);
if (empty($ticket)) {
$url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token={$accessToken}&type=jsapi";
$response = file_get_contents($url);
$result = json_decode($response, true);
if (!isset($result['ticket'])) {
throw new \Exception('获取ticket失败');
}
$ticket = $result['ticket'];
Cache($cacheKey, $ticket, $result['expires_in'] - 60);
}
return $ticket;
}
private function createNonceStr(): string
{
$characters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
$nonceStr = '';
$length = $length ?? 16; // 如果没有传入$length,则默认为16
for ($i = 0; $i < $length; $i++) {
$nonceStr .= $characters[rand(0, strlen($characters) - 1)];
}
return $nonceStr;
}
private function paySign($nonceStr, $package,$timestamp): string {
$apikey = 'aslkdjlk238dalksjd193hkjahskjdf2';
$params = [
'appId' => 'appid',
'timeStamp' => $timestamp,
'nonceStr' => $nonceStr,
'package' => $package,
'signType' => 'MD5',
];
ksort($params, SORT_STRING);
$stringSignTemp = '';
foreach ($params as $k => $v) {
if ($v !== '') { // 排除空值
$stringSignTemp .= $k . '=' . $v . '&';
}
}
// 添加 API 密钥
$stringSignTemp .= 'key=' . $apikey;
// 使用MD5算法生成签名
$sign = md5($stringSignTemp);
// 转换为大写
// return strtoupper($sign);
return strtoupper($sign);
}
微信定位+高德获取地理逆编码
js
const setLocation = async () => {
let url = '';
const href = window.location.href;
url = href.split('#')[0];//截取当前地址栏的URL,(不带#hash部分)
let res = await axios.post('user/Location', {
url: url,
id: userStore.id
});//通过接口获取微信配置所需信息
if (res.data.code === 200) {
alert.success('请求成功')
wx.config({
debug: false,
appId: res.data.appid, // 必填,公众号的唯一标识
timestamp: res.data.timestamp, // 必填,生成签名的时间戳
nonceStr: res.data.noncestr, // 必填,生成签名的随机串
signature: res.data.signature, // 必填,签名
jsApiList: ['getLocation'],//必填,需要使用的JS接口列表,如我要用的是wx.getLocation,那么我的jsApiList里的就是getLocation
});
wx.ready(function () {
wx.getLocation({
type: 'wgs84', // 默认为wgs84的gps坐标,如果要返回直接给openLocation用的火星坐标,可传入'gcj02'
success: function (res) {
const latitude = res.latitude; // 纬度,浮点数,范围为90 ~ -90
const longitude = res.longitude; // 经度,浮点数,范围为180 ~ -180。
AMap.plugin('AMap.Geocoder', function () {
const geocoder = new AMap.Geocoder({
// city 指定进行编码查询的城市,支持传入城市名、adcode 和 citycode
city: '全国',
radius: 100 //范围,默认:500
});
const lnglat = [longitude, latitude];
geocoder.getAddress(lnglat, function (status, result) {
if (status === 'complete' && result.info === 'OK') {
selectedOptions.value = [result.regeocode.addressComponent.province, result.regeocode.addressComponent.city, result.regeocode.addressComponent.district]
detail.value = result.regeocode.formattedAddress
alert.success('定位成功!')
} else {
alert('定位失败!')
}
})
})
// 获取定位位置信息成功,可以进行后续操作
},
fail: function (err) {
alert("获取定位位置信息失败!")
},
cancel: function (res) {
alert('用户拒绝授权获取地理位置');
}
});
});
} else {
alert("获取失败")
}
}
定位wx.ready那几个参数生成与之前的类似
main.js内配置
initAMapApiLoader({
key: '',
plugins: ['AMap.Geocoder'],
securityJsCode: ''
})