wlzboy
2025-09-22 fe95f471666d93e7822a4886c1c69dafbd6b2a1e
feat:更新保存
3个文件已添加
6个文件已修改
669 ■■■■■ 已修改文件
.gitignore 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
966120App 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/com/ruoyi/web/controller/evaluation/EvaluationController.java 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/resources/application.yml 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/src/main/java/com/ruoyi/common/utils/WechatUtils.java 72 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/src/api/evaluation.js 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/src/views/evaluation/index.vue 188 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
微信授权功能使用说明.md 124 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
微信授权调试指南.md 209 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.gitignore
@@ -45,4 +45,5 @@
!*/build/*.java
!*/build/*.html
!*/build/*.xml
node_modules/
node_modules/
966120App/
966120App
New file
@@ -0,0 +1 @@
D:/project/急救转运/code/966120
ruoyi-admin/src/main/java/com/ruoyi/web/controller/evaluation/EvaluationController.java
@@ -1,5 +1,6 @@
package com.ruoyi.web.controller.evaluation;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
@@ -156,6 +157,38 @@
    private String wechatAppSecret;
    /**
     * ç”Ÿæˆå¾®ä¿¡æŽˆæƒURL
     */
    @Anonymous
    @GetMapping("/wechat/authurl")
    public AjaxResult getWechatAuthUrl(String redirectUri, String state) {
        try {
            if (StringUtils.isEmpty(redirectUri)) {
                return error("回调地址不能为空");
            }
            logger.info("生成微信授权URL - åŽŸå§‹redirectUri: {}", redirectUri);
            // ç”Ÿæˆå¾®ä¿¡æŽˆæƒURL,使用snsapi_userinfo获取用户信息
            String authUrl = WechatUtils.generateAuthUrl(wechatAppId, redirectUri, "snsapi_userinfo", state);
            if (authUrl == null) {
                return error("生成微信授权URL失败");
            }
            logger.info("生成微信授权URL成功: {}", authUrl);
            Map<String, String> result = new HashMap<>();
            result.put("authUrl", authUrl);
            result.put("originalRedirectUri", redirectUri);
            result.put("appId", wechatAppId);
            return success(result);
        } catch (Exception e) {
            logger.error("生成微信授权URL失败", e);
            return error("生成微信授权URL失败: " + e.getMessage());
        }
    }
    /**
     * èŽ·å–å¾®ä¿¡ç”¨æˆ·ä¿¡æ¯
     */
    @Anonymous
@@ -163,7 +196,7 @@
    public AjaxResult getWechatUserInfo(String code, HttpServletRequest request) {
        try {
            
            if (StringUtils.isEmpty(code)) {
            if (code.isEmpty()) {
                return error("授权码不能为空");
            }
@@ -182,7 +215,22 @@
                return error("获取微信用户信息失败");
            }
            return success(userInfo);
            // å¤„理用户信息,确保字段名称一致
            Map<String, Object> result = new HashMap<>();
            result.put("openid", userInfo.getString("openid"));
            result.put("nickname", userInfo.getString("nickname"));
            result.put("headimgurl", userInfo.getString("headimgurl"));
            result.put("sex", userInfo.getInteger("sex"));
            result.put("province", userInfo.getString("province"));
            result.put("city", userInfo.getString("city"));
            result.put("country", userInfo.getString("country"));
            result.put("unionid", userInfo.getString("unionid"));
            // æ³¨æ„ï¼šå¾®ä¿¡ç½‘页授权无法直接获取手机号,需要通过其他方式
            // å¦‚需获取手机号,需要使用微信小程序的getPhoneNumber接口或微信开放平台的手机号快速验证组件
            result.put("phone", "");
            result.put("phoneNote", "微信网页授权无法直接获取手机号,请手动输入");
            return success(result);
        } catch (Exception e) {
            logger.error("获取微信用户信息失败", e);
            return error("获取微信用户信息失败");
ruoyi-admin/src/main/resources/application.yml
@@ -147,6 +147,11 @@
# å¾®ä¿¡é…ç½®
wechat:
  appId: your_wechat_appid
  appSecret: your_wechat_appsecret
  redirectUri: http://yourdomain.com/evaluation
  appId: wx70f6a7346ee842c0
  appSecret: 2d6c59de85e876b7eadebeba62e5417a
  redirectUri: http://yourdomain.com/evaluation
  # å¼€å‘环境配置
  dev:
    enabled: true  # æ˜¯å¦å¯ç”¨å¼€å‘模式
    mockUserInfo: true  # æ˜¯å¦æ¨¡æ‹Ÿç”¨æˆ·ä¿¡æ¯
    ngrokUrl: http://your-ngrok-url.ngrok.io  # å†…网穿透地址
ruoyi-common/src/main/java/com/ruoyi/common/utils/WechatUtils.java
@@ -4,7 +4,7 @@
import com.alibaba.fastjson2.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.client.RestTemplate;
import com.ruoyi.common.utils.http.HttpUtils;
import java.util.HashMap;
import java.util.Map;
@@ -18,7 +18,8 @@
    
    private static final Logger log = LoggerFactory.getLogger(WechatUtils.class);
    
    private static final String WECHAT_API_BASE_URL = "https://api.weixin.qq.com";
    private static final String WECHAT_API_BASE_URL = "https://open.weixin.qq.com";
    private static final String WECHAT_API_BASE_URL_SERVER = "https://api.weixin.qq.com";
    
    /**
     * èŽ·å–å¾®ä¿¡Access Token
@@ -29,10 +30,10 @@
     */
    public static String getAccessToken(String appId, String appSecret) {
        try {
            String url = WECHAT_API_BASE_URL + "/cgi-bin/token?grant_type=client_credential&appid=" + appId + "&secret=" + appSecret;
            String url = WECHAT_API_BASE_URL_SERVER + "/cgi-bin/token";
            String param = "grant_type=client_credential&appid=" + appId + "&secret=" + appSecret;
            
            RestTemplate restTemplate = new RestTemplate();
            String response = restTemplate.getForObject(url, String.class);
            String response = HttpUtils.sendGet(url, param);
            
            JSONObject jsonObject = JSON.parseObject(response);
            if (jsonObject.containsKey("access_token")) {
@@ -56,10 +57,10 @@
     */
    public static JSONObject getWechatUserInfo(String accessToken, String openid) {
        try {
            String url = WECHAT_API_BASE_URL + "/cgi-bin/user/info?access_token=" + accessToken + "&openid=" + openid + "&lang=zh_CN";
            String url = WECHAT_API_BASE_URL_SERVER + "/cgi-bin/user/info";
            String param = "access_token=" + accessToken + "&openid=" + openid + "&lang=zh_CN";
            
            RestTemplate restTemplate = new RestTemplate();
            String response = restTemplate.getForObject(url, String.class);
            String response = HttpUtils.sendGet(url, param);
            
            JSONObject jsonObject = JSON.parseObject(response);
            if (jsonObject.containsKey("openid")) {
@@ -84,10 +85,10 @@
     */
    public static JSONObject getWebAccessToken(String appId, String appSecret, String code) {
        try {
            String url = WECHAT_API_BASE_URL + "/sns/oauth2/access_token?appid=" + appId + "&secret=" + appSecret + "&code=" + code + "&grant_type=authorization_code";
            String url = WECHAT_API_BASE_URL_SERVER + "/sns/oauth2/access_token";
            String param = "appid=" + appId + "&secret=" + appSecret + "&code=" + code + "&grant_type=authorization_code";
            
            RestTemplate restTemplate = new RestTemplate();
            String response = restTemplate.getForObject(url, String.class);
            String response = HttpUtils.sendGet(url, param);
            
            JSONObject jsonObject = JSON.parseObject(response);
            if (jsonObject.containsKey("access_token")) {
@@ -111,10 +112,10 @@
     */
    public static JSONObject getWebUserInfo(String accessToken, String openid) {
        try {
            String url = WECHAT_API_BASE_URL + "/sns/userinfo?access_token=" + accessToken + "&openid=" + openid + "&lang=zh_CN";
            String url = WECHAT_API_BASE_URL_SERVER + "/sns/userinfo";
            String param = "access_token=" + accessToken + "&openid=" + openid + "&lang=zh_CN";
            
            RestTemplate restTemplate = new RestTemplate();
            String response = restTemplate.getForObject(url, String.class);
            String response = HttpUtils.sendGet(url, param);
            
            JSONObject jsonObject = JSON.parseObject(response);
            if (jsonObject.containsKey("openid")) {
@@ -140,8 +141,16 @@
     */
    public static String generateAuthUrl(String appId, String redirectUri, String scope, String state) {
        try {
            String encodedRedirectUri = java.net.URLEncoder.encode(redirectUri, "UTF-8");
            return WECHAT_API_BASE_URL + "/connect/oauth2/authorize?appid=" + appId + "&redirect_uri=" + encodedRedirectUri + "&response_type=code&scope=" + scope + "&state=" + state + "#wechat_redirect";
            // æ¸…理和验证redirectUri
            String cleanRedirectUri = cleanRedirectUri(redirectUri);
            // URL编码
            String encodedRedirectUri = java.net.URLEncoder.encode(cleanRedirectUri, "UTF-8");
            return WECHAT_API_BASE_URL + "/connect/oauth2/authorize?appid=" + appId +
                   "&redirect_uri=" + encodedRedirectUri +
                   "&response_type=code&scope=" + scope +
                   "&state=" + state + "#wechat_redirect";
        } catch (Exception e) {
            log.error("生成微信授权URL异常: {}", e.getMessage());
            return null;
@@ -149,6 +158,37 @@
    }
    
    /**
     * æ¸…理和验证redirectUri
     *
     * @param redirectUri åŽŸå§‹å›žè°ƒåœ°å€
     * @return æ¸…理后的回调地址
     */
    private static String cleanRedirectUri(String redirectUri) {
        if (StringUtils.isEmpty(redirectUri)) {
            return redirectUri;
        }
        try {
            // ç§»é™¤ç«¯å£å·ï¼ˆå¾®ä¿¡æŽˆæƒä¸æ”¯æŒç«¯å£å·ï¼‰
            if (redirectUri.contains(":81") || redirectUri.contains(":8080") || redirectUri.contains(":3000")) {
                redirectUri = redirectUri.replaceAll(":(81|8080|3000|8081|8082|8083|8084|8085|8086|8087|8088|8089|8090)", "");
                log.warn("检测到端口号,已自动移除: {}", redirectUri);
            }
            // ç¡®ä¿ä½¿ç”¨HTTPS(生产环境)
            if (redirectUri.startsWith("http://") && !redirectUri.contains("localhost") && !redirectUri.contains("127.0.0.1")) {
                redirectUri = redirectUri.replace("http://", "https://");
                log.warn("生产环境建议使用HTTPS,已自动转换: {}", redirectUri);
            }
            return redirectUri;
        } catch (Exception e) {
            log.error("清理redirectUri异常: {}", e.getMessage());
            return redirectUri;
        }
    }
    /**
     * åˆ¤æ–­æ˜¯å¦ä¸ºå¾®ä¿¡æµè§ˆå™¨
     * 
     * @param userAgent ç”¨æˆ·ä»£ç†å­—符串
ruoyi-ui/src/api/evaluation.js
@@ -17,6 +17,15 @@
  })
}
// ç”Ÿæˆå¾®ä¿¡æŽˆæƒURL
export function getWechatAuthUrl(redirectUri, state) {
  return request({
    url: '/evaluation/wechat/authurl',
    method: 'get',
    params: { redirectUri, state }
  })
}
// èŽ·å–å¾®ä¿¡ç”¨æˆ·ä¿¡æ¯
export function getWechatUserInfo(code) {
  return request({
ruoyi-ui/src/views/evaluation/index.vue
@@ -20,11 +20,41 @@
        <!-- å®¢æˆ·ä¿¡æ¯ -->
        <div class="form-section">
          <h3>客户信息</h3>
          <!-- å¾®ä¿¡æŽˆæƒä¿¡æ¯æ˜¾ç¤º -->
          <div v-if="evaluationForm.wechatOpenid" class="wechat-info">
            <div class="wechat-user">
              <img v-if="evaluationForm.wechatAvatar" :src="evaluationForm.wechatAvatar" class="wechat-avatar" />
              <div class="wechat-details">
                <div class="wechat-nickname">{{ evaluationForm.wechatNickname }}</div>
                <div class="wechat-openid">已授权微信登录</div>
              </div>
            </div>
          </div>
          <!-- å¾®ä¿¡æŽˆæƒæŒ‰é’® -->
          <div v-if="isWechatBrowser() && !evaluationForm.wechatOpenid" class="wechat-auth-section">
            <el-button
              type="primary"
              size="small"
              @click="redirectToWechatAuth"
              icon="el-icon-user"
              :loading="wechatAuthLoading"
            >
              {{ wechatAuthLoading ? '正在跳转...' : '微信一键登录' }}
            </el-button>
            <div class="wechat-tip">点击可自动获取您的微信信息</div>
          </div>
          <el-form-item label="姓名" prop="customerName">
            <el-input v-model="evaluationForm.customerName" placeholder="请输入您的姓名" />
          </el-form-item>
          <el-form-item label="手机号" prop="customerPhone">
            <el-input v-model="evaluationForm.customerPhone" placeholder="请输入您的手机号" />
            <div v-if="evaluationForm.wechatOpenid" class="phone-tip">
              <i class="el-icon-info"></i>
              å¾®ä¿¡æŽˆæƒæ— æ³•直接获取手机号,请手动输入
            </div>
          </el-form-item>
        </div>
@@ -118,7 +148,7 @@
</template>
<script>
import { getEvaluationDimensions, submitEvaluation, getWechatUserInfo } from "@/api/evaluation";
import { getEvaluationDimensions, submitEvaluation, getWechatUserInfo, getWechatAuthUrl } from "@/api/evaluation";
export default {
  name: "Evaluation",
@@ -147,7 +177,8 @@
      },
      submitting: false,
      showResult: false,
      resultMessage: ''
      resultMessage: '',
      wechatAuthLoading: false
    };
  },
  created() {
@@ -178,7 +209,10 @@
    // å¤„理微信授权
    async handleWechatAuth() {
      const code = this.$route.query.code;
      const state = this.$route.query.state;
      if (code) {
        // æœ‰æŽˆæƒç ï¼ŒèŽ·å–ç”¨æˆ·ä¿¡æ¯
        try {
          const response = await getWechatUserInfo(code);
          if (response.code === 200) {
@@ -187,10 +221,59 @@
            this.evaluationForm.wechatNickname = userInfo.nickname;
            this.evaluationForm.wechatAvatar = userInfo.headimgurl;
            this.evaluationForm.wechatPhone = userInfo.phone || '';
            // å¦‚果获取到了微信昵称,自动填充到姓名字段
            if (userInfo.nickname && !this.evaluationForm.customerName) {
              this.evaluationForm.customerName = userInfo.nickname;
            }
            this.$message.success('微信授权成功,已自动获取您的信息');
          } else {
            this.$message.error(response.msg || '获取微信用户信息失败');
          }
        } catch (error) {
          console.error('获取微信用户信息失败:', error);
          this.$message.error('获取微信用户信息失败,请重试');
        }
      } else {
        // æ²¡æœ‰æŽˆæƒç ï¼Œæ£€æŸ¥æ˜¯å¦éœ€è¦è·³è½¬åˆ°å¾®ä¿¡æŽˆæƒ
        const hasWechatInfo = this.evaluationForm.wechatOpenid;
        if (!hasWechatInfo) {
          await this.redirectToWechatAuth();
        }
      }
    },
    // è·³è½¬åˆ°å¾®ä¿¡æŽˆæƒ
    async redirectToWechatAuth() {
      this.wechatAuthLoading = true;
      try {
        // æž„建当前页面的完整URL作为回调地址
        const currentUrl = window.location.origin + window.location.pathname;
        const params = new URLSearchParams(window.location.search);
        params.delete('code'); // ç§»é™¤å¯èƒ½å­˜åœ¨çš„code参数
        params.delete('state'); // ç§»é™¤å¯èƒ½å­˜åœ¨çš„state参数
        const redirectUri = currentUrl + (params.toString() ? '?' + params.toString() : '');
        console.log('准备生成微信授权URL,回调地址:', redirectUri);
        const response = await getWechatAuthUrl(redirectUri, 'evaluation');
        if (response.code === 200) {
          console.log('微信授权URL生成成功:', response.data);
          console.log('原始回调地址:', response.data.originalRedirectUri);
          console.log('微信AppID:', response.data.appId);
          // è·³è½¬åˆ°å¾®ä¿¡æŽˆæƒé¡µé¢
          window.location.href = response.data.authUrl;
        } else {
          console.error('生成微信授权URL失败:', response.msg);
          this.$message.error('微信授权服务暂时不可用,请手动填写信息');
        }
      } catch (error) {
        console.error('跳转微信授权失败:', error);
        this.$message.error('微信授权失败,请手动填写信息');
      } finally {
        this.wechatAuthLoading = false;
      }
    },
@@ -465,6 +548,72 @@
  min-height: 60px !important;
}
/* å¾®ä¿¡æŽˆæƒç›¸å…³æ ·å¼ */
.wechat-info {
  background: #f0f9ff;
  border: 1px solid #b3d8ff;
  border-radius: 6px;
  padding: 12px;
  margin-bottom: 16px;
}
.wechat-user {
  display: flex;
  align-items: center;
}
.wechat-avatar {
  width: 40px;
  height: 40px;
  border-radius: 50%;
  margin-right: 12px;
  border: 2px solid #409EFF;
}
.wechat-details {
  flex: 1;
}
.wechat-nickname {
  font-weight: bold;
  color: #333;
  font-size: 14px;
  margin-bottom: 4px;
}
.wechat-openid {
  color: #666;
  font-size: 12px;
}
.wechat-auth-section {
  text-align: center;
  padding: 16px;
  background: #f8f9fa;
  border-radius: 6px;
  margin-bottom: 16px;
  border: 1px dashed #ddd;
}
.wechat-tip {
  color: #666;
  font-size: 12px;
  margin-top: 8px;
}
.phone-tip {
  color: #909399;
  font-size: 12px;
  margin-top: 4px;
  display: flex;
  align-items: center;
}
.phone-tip i {
  margin-right: 4px;
  color: #409EFF;
}
/* ä¼˜åŒ–表单项间距 */
.el-form-item {
  margin-bottom: 12px;
@@ -628,6 +777,41 @@
    padding: 8px 24px;
    font-size: 13px;
  }
  /* ç§»åŠ¨ç«¯å¾®ä¿¡æŽˆæƒæ ·å¼ */
  .wechat-info {
    padding: 10px;
    margin-bottom: 12px;
  }
  .wechat-avatar {
    width: 36px;
    height: 36px;
    margin-right: 10px;
  }
  .wechat-nickname {
    font-size: 13px;
  }
  .wechat-openid {
    font-size: 11px;
  }
  .wechat-auth-section {
    padding: 12px;
    margin-bottom: 12px;
  }
  .wechat-tip {
    font-size: 11px;
    margin-top: 6px;
  }
  .phone-tip {
    font-size: 11px;
    margin-top: 3px;
  }
}
/* è¶…小屏幕适配 */
΢ÐÅÊÚȨ¹¦ÄÜʹÓÃ˵Ã÷.md
New file
@@ -0,0 +1,124 @@
# å¾®ä¿¡æŽˆæƒåŠŸèƒ½ä½¿ç”¨è¯´æ˜Ž
## åŠŸèƒ½æ¦‚è¿°
本功能实现了在评价页面中获取微信用户的OpenId、头像、昵称等信息,提升用户体验。
## å®žçŽ°çš„åŠŸèƒ½
### 1. å¾®ä¿¡æŽˆæƒURL生成
- åŽç«¯æŽ¥å£ï¼š`GET /evaluation/wechat/authurl`
- åŠŸèƒ½ï¼šç”Ÿæˆå¾®ä¿¡ç½‘é¡µæŽˆæƒURL,使用`snsapi_userinfo`权限获取用户基本信息
### 2. å¾®ä¿¡ç”¨æˆ·ä¿¡æ¯èŽ·å–
- åŽç«¯æŽ¥å£ï¼š`GET /evaluation/wechat/userinfo`
- åŠŸèƒ½ï¼šé€šè¿‡æŽˆæƒç èŽ·å–å¾®ä¿¡ç”¨æˆ·ä¿¡æ¯ï¼ŒåŒ…æ‹¬ï¼š
  - OpenId:用户唯一标识
  - æ˜µç§°ï¼šç”¨æˆ·å¾®ä¿¡æ˜µç§°
  - å¤´åƒï¼šç”¨æˆ·å¾®ä¿¡å¤´åƒURL
  - æ€§åˆ«ã€çœä»½ã€åŸŽå¸‚、国家等基本信息
### 3. å‰ç«¯è‡ªåŠ¨æŽˆæƒæµç¨‹
- æ£€æµ‹å¾®ä¿¡æµè§ˆå™¨çŽ¯å¢ƒ
- è‡ªåŠ¨è·³è½¬åˆ°å¾®ä¿¡æŽˆæƒé¡µé¢
- æŽˆæƒæˆåŠŸåŽè‡ªåŠ¨èŽ·å–ç”¨æˆ·ä¿¡æ¯
- è‡ªåŠ¨å¡«å……ç”¨æˆ·æ˜µç§°åˆ°å§“åå­—æ®µ
## é…ç½®è¦æ±‚
### 1. å¾®ä¿¡å…¬ä¼—平台配置
在`application.yml`中配置微信AppID和AppSecret:
```yaml
wechat:
  appId: your_wechat_appid
  appSecret: your_wechat_appsecret
```
### 2. å¾®ä¿¡å…¬ä¼—平台网页授权域名配置
需要在微信公众平台后台配置授权回调域名,例如:
- `yourdomain.com`
## ä½¿ç”¨æµç¨‹
### 1. ç”¨æˆ·è®¿é—®è¯„价页面
- åœ¨å¾®ä¿¡æµè§ˆå™¨ä¸­æ‰“开评价页面
- ç³»ç»Ÿè‡ªåŠ¨æ£€æµ‹å¾®ä¿¡çŽ¯å¢ƒ
### 2. å¾®ä¿¡æŽˆæƒ
- å¦‚果用户未授权,显示"微信一键登录"按钮
- ç‚¹å‡»æŒ‰é’®è·³è½¬åˆ°å¾®ä¿¡æŽˆæƒé¡µé¢
- ç”¨æˆ·ç¡®è®¤æŽˆæƒåŽè¿”回评价页面
### 3. ä¿¡æ¯è‡ªåЍ填充
- æŽˆæƒæˆåŠŸåŽè‡ªåŠ¨èŽ·å–ç”¨æˆ·ä¿¡æ¯
- è‡ªåŠ¨å¡«å……æ˜µç§°åˆ°å§“åå­—æ®µ
- æ˜¾ç¤ºç”¨æˆ·å¤´åƒå’ŒæŽˆæƒçŠ¶æ€
### 4. æ‰‹åŠ¨è¾“å…¥æ‰‹æœºå·
- ç”±äºŽå¾®ä¿¡ç½‘页授权无法直接获取手机号
- ç”¨æˆ·éœ€è¦æ‰‹åŠ¨è¾“å…¥æ‰‹æœºå·
- ç³»ç»Ÿä¼šæ˜¾ç¤ºæç¤ºä¿¡æ¯è¯´æ˜ŽåŽŸå› 
## æŠ€æœ¯å®žçް
### åŽç«¯å®žçް
1. **WechatUtils工具类**:封装微信API调用
2. **EvaluationController**:提供微信授权相关接口
3. **网页授权流程**:使用OAuth2.0标准流程
### å‰ç«¯å®žçް
1. **自动检测微信环境**:通过User-Agent判断
2. **授权URL生成**:动态构建回调地址
3. **用户信息展示**:美观的UI展示授权状态
4. **错误处理**:完善的错误提示和降级方案
## æ³¨æ„äº‹é¡¹
### 1. æ‰‹æœºå·èŽ·å–é™åˆ¶
- å¾®ä¿¡ç½‘页授权无法直接获取用户手机号
- å¦‚需获取手机号,需要:
  - ä½¿ç”¨å¾®ä¿¡å°ç¨‹åºçš„`getPhoneNumber`接口
  - æˆ–使用微信开放平台的手机号快速验证组件
### 2. æŽˆæƒåŸŸåé…ç½®
- å¿…须在微信公众平台配置正确的授权回调域名
- åŸŸåå¿…须与实际访问域名一致
### 3. HTTPS要求
- ç”Ÿäº§çŽ¯å¢ƒå¿…é¡»ä½¿ç”¨HTTPS协议
- å¾®ä¿¡æŽˆæƒè¦æ±‚安全连接
### 4. ç”¨æˆ·ä½“验优化
- æä¾›æ‰‹åŠ¨è¾“å…¥é€‰é¡¹ä½œä¸ºé™çº§æ–¹æ¡ˆ
- æ¸…晰的提示信息说明授权状态
- å“åº”式设计适配移动端
## æ‰©å±•功能
### 1. å¾®ä¿¡å°ç¨‹åºé›†æˆ
如需在小程序中使用,可以:
- ä½¿ç”¨`wx.getUserProfile`获取用户信息
- ä½¿ç”¨`wx.getPhoneNumber`获取手机号
### 2. å¾®ä¿¡å¼€æ”¾å¹³å°é›†æˆ
如需获取更多信息,可以:
- ç”³è¯·å¾®ä¿¡å¼€æ”¾å¹³å°è´¦å·
- ä½¿ç”¨æ‰‹æœºå·å¿«é€ŸéªŒè¯ç»„ä»¶
- èŽ·å–UnionID进行多平台用户统一
## æµ‹è¯•建议
1. **功能测试**
   - åœ¨å¾®ä¿¡æµè§ˆå™¨ä¸­æµ‹è¯•完整授权流程
   - æµ‹è¯•授权成功和失败的情况
   - æµ‹è¯•非微信浏览器的降级方案
2. **兼容性测试**
   - æµ‹è¯•不同版本的微信浏览器
   - æµ‹è¯•不同设备的显示效果
   - æµ‹è¯•网络异常情况
3. **安全性测试**
   - éªŒè¯æŽˆæƒç çš„æœ‰æ•ˆæ€§
   - æµ‹è¯•恶意请求的处理
   - éªŒè¯ç”¨æˆ·ä¿¡æ¯çš„完整性
΢ÐÅÊÚȨµ÷ÊÔÖ¸ÄÏ.md
New file
@@ -0,0 +1,209 @@
# å¾®ä¿¡æŽˆæƒè°ƒè¯•指南
## é—®é¢˜åˆ†æž
您遇到的 `redirect_uri å‚数错误` é—®é¢˜ï¼Œä¸»è¦åŽŸå› å¦‚ä¸‹ï¼š
### 1. åŸŸåé…ç½®é—®é¢˜
从您的URL可以看出:
```
redirect_uri=http%3A%2F%2Fwx.966120.com.cn%3A81%2Fevaluation%3Fvehicle%3D%25E7%25B2%25AC12345
```
**问题:**
- åŸŸå `wx.966120.com.cn:81` å¯èƒ½æœªåœ¨å¾®ä¿¡å…¬ä¼—平台配置
- ç«¯å£å· `:81` åœ¨å¾®ä¿¡æŽˆæƒä¸­ä¸è¢«æ”¯æŒ
- éœ€è¦é…ç½®æ­£ç¡®çš„æŽˆæƒå›žè°ƒåŸŸå
### 2. å¾®ä¿¡å…¬ä¼—平台配置要求
#### ç½‘页授权域名配置
1. ç™»å½•微信公众平台 (https://mp.weixin.qq.com)
2. è¿›å…¥"设置与开发" -> "公众号设置" -> "功能设置"
3. åœ¨"网页授权域名"中添加:`wx.966120.com.cn`
4. **注意:不要包含端口号,不要包含协议**
## è§£å†³æ–¹æ¡ˆ
### æ–¹æ¡ˆä¸€ï¼šä¿®å¤åŸŸåé…ç½®ï¼ˆæŽ¨èï¼‰
#### 1. å¾®ä¿¡å…¬ä¼—平台配置
```
网页授权域名:wx.966120.com.cn
```
#### 2. ä¿®æ”¹åº”用配置
在 `application.yml` ä¸­é…ç½®æ­£ç¡®çš„域名:
```yaml
wechat:
  appId: wx70f6a7346ee842c0
  appSecret: 2d6c59de85e876b7eadebeba62e5417a
  redirectUri: https://wx.966120.com.cn/evaluation  # ä½¿ç”¨HTTPS,无端口号
```
#### 3. æœåŠ¡å™¨é…ç½®
- ç¡®ä¿ `wx.966120.com.cn` åŸŸåæŒ‡å‘您的服务器
- é…ç½®HTTPS证书(微信授权要求HTTPS)
- é…ç½®åå‘代理,将80/443端口转发到您的应用端口
### æ–¹æ¡ˆäºŒï¼šå¼€å‘环境调试
#### 1. ä½¿ç”¨å†…网穿透工具
**推荐工具:**
- **ngrok** (免费版)
- **natapp** (国内访问更稳定)
- **frp** (自建)
**ngrok使用步骤:**
```bash
# 1. ä¸‹è½½ngrok
# 2. æ³¨å†Œè´¦å·èŽ·å–authtoken
ngrok authtoken YOUR_AUTHTOKEN
# 3. å¯åŠ¨å†…ç½‘ç©¿é€
ngrok http 8080
# 4. èŽ·å¾—å…¬ç½‘åœ°å€ï¼Œå¦‚ï¼šhttps://abc123.ngrok.io
```
#### 2. é…ç½®å¼€å‘环境
```yaml
wechat:
  appId: wx70f6a7346ee842c0
  appSecret: 2d6c59de85e876b7eadebeba62e5417a
  redirectUri: https://your-ngrok-url.ngrok.io/evaluation
```
#### 3. å¾®ä¿¡å…¬ä¼—平台配置
在网页授权域名中添加:`your-ngrok-url.ngrok.io`
### æ–¹æ¡ˆä¸‰ï¼šæœ¬åœ°å¼€å‘模拟
#### 1. æ·»åŠ å¼€å‘çŽ¯å¢ƒé…ç½®
```yaml
wechat:
  appId: wx70f6a7346ee842c0
  appSecret: 2d6c59de85e876b7eadebeba62e5417a
  redirectUri: https://wx.966120.com.cn/evaluation
  # å¼€å‘环境配置
  dev:
    enabled: true
    mockUserInfo: true
    ngrokUrl: https://your-ngrok-url.ngrok.io
```
#### 2. ä¿®æ”¹åŽç«¯ä»£ç æ”¯æŒå¼€å‘模式
```java
@Value("${wechat.dev.enabled:false}")
private boolean devModeEnabled;
@Value("${wechat.dev.mockUserInfo:false}")
private boolean mockUserInfo;
// åœ¨ç”ŸæˆæŽˆæƒURL时添加开发模式判断
if (devModeEnabled && mockUserInfo) {
    // è¿”回模拟的授权URL,直接跳转到回调地址
    String mockCode = "dev_mock_code_" + System.currentTimeMillis();
    String mockRedirectUri = redirectUri + "?code=" + mockCode + "&state=" + state;
    return success(Map.of("authUrl", mockRedirectUri));
}
```
## è°ƒè¯•步骤
### 1. æ£€æŸ¥å½“前配置
```bash
# æ£€æŸ¥åº”用配置
curl -X GET "http://localhost:8080/evaluation/wechat/authurl?redirectUri=http://wx.966120.com.cn/evaluation&state=test"
```
### 2. éªŒè¯å¾®ä¿¡æŽˆæƒURL
生成的URL应该是这样的格式:
```
https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx70f6a7346ee842c0&redirect_uri=https%3A%2F%2Fwx.966120.com.cn%2Fevaluation&response_type=code&scope=snsapi_userinfo&state=test#wechat_redirect
```
### 3. æ£€æŸ¥åŸŸåè§£æž
```bash
# æ£€æŸ¥åŸŸåæ˜¯å¦è§£æžæ­£ç¡®
nslookup wx.966120.com.cn
ping wx.966120.com.cn
```
### 4. æ£€æŸ¥HTTPS证书
```bash
# æ£€æŸ¥HTTPS是否正常
curl -I https://wx.966120.com.cn
```
## å¸¸è§é”™è¯¯åŠè§£å†³æ–¹æ¡ˆ
### 1. redirect_uri å‚数错误
**原因:** åŸŸåæœªåœ¨å¾®ä¿¡å…¬ä¼—平台配置或配置错误
**解决:** åœ¨å¾®ä¿¡å…¬ä¼—平台正确配置网页授权域名
### 2. åŸŸåä¸åŒ¹é…
**原因:** å›žè°ƒåœ°å€ä¸Žé…ç½®çš„æŽˆæƒåŸŸåä¸ä¸€è‡´
**解决:** ç¡®ä¿å›žè°ƒåœ°å€çš„域名与配置的授权域名完全一致
### 3. ç«¯å£å·é—®é¢˜
**原因:** å¾®ä¿¡æŽˆæƒä¸æ”¯æŒç«¯å£å·
**解决:** ä½¿ç”¨åå‘代理,将80/443端口转发到应用端口
### 4. HTTPS要求
**原因:** ç”Ÿäº§çŽ¯å¢ƒå¿…é¡»ä½¿ç”¨HTTPS
**解决:** é…ç½®SSL证书,使用HTTPS协议
## æµ‹è¯•验证
### 1. æœ¬åœ°æµ‹è¯•
```bash
# å¯åŠ¨åº”ç”¨
mvn spring-boot:run
# æµ‹è¯•授权URL生成
curl "http://localhost:8080/evaluation/wechat/authurl?redirectUri=https://wx.966120.com.cn/evaluation&state=test"
```
### 2. å¾®ä¿¡çŽ¯å¢ƒæµ‹è¯•
1. åœ¨å¾®ä¿¡ä¸­æ‰“开生成的授权URL
2. ç¡®è®¤èƒ½æ­£å¸¸è·³è½¬åˆ°å¾®ä¿¡æŽˆæƒé¡µé¢
3. æŽˆæƒåŽèƒ½æ­£å¸¸å›žè°ƒåˆ°æ‚¨çš„应用
### 3. ç”¨æˆ·ä¿¡æ¯èŽ·å–æµ‹è¯•
```bash
# ä½¿ç”¨æŽˆæƒç èŽ·å–ç”¨æˆ·ä¿¡æ¯
curl "http://localhost:8080/evaluation/wechat/userinfo?code=YOUR_AUTH_CODE"
```
## æ³¨æ„äº‹é¡¹
1. **域名配置**:必须在微信公众平台配置正确的授权域名
2. **HTTPS要求**:生产环境必须使用HTTPS
3. **端口号限制**:微信授权不支持端口号
4. **编码问题**:确保URL编码正确
5. **测试环境**:开发时可以使用内网穿透工具
## æŽ¨èé…ç½®
### ç”Ÿäº§çŽ¯å¢ƒ
```yaml
wechat:
  appId: wx70f6a7346ee842c0
  appSecret: 2d6c59de85e876b7eadebeba62e5417a
  redirectUri: https://wx.966120.com.cn/evaluation
```
### å¼€å‘环境
```yaml
wechat:
  appId: wx70f6a7346ee842c0
  appSecret: 2d6c59de85e876b7eadebeba62e5417a
  redirectUri: https://your-ngrok-url.ngrok.io/evaluation
  dev:
    enabled: true
    mockUserInfo: true
```
按照以上步骤配置后,您的微信授权功能应该能正常工作。