Răsfoiți Sursa

图片上传配置

liuchengsen 1 lună în urmă
părinte
comite
cbe8b71451

+ 4 - 4
zhijiayun-gateway/src/main/resources/application-prod.yml

@@ -68,10 +68,10 @@ sms:
 
 # 阿里云OSS配置(生产环境)
 oss:
-  endpoint: ${OSS_ENDPOINT:oss-cn-hangzhou.aliyuncs.com}
-  access-key-id: ${ALIBABA_CLOUD_ACCESS_KEY_ID:}
-  access-key-secret: ${ALIBABA_CLOUD_ACCESS_KEY_SECRET:}
-  bucket-name: ${OSS_BUCKET_NAME:zhijiayun-pharmacy}
+  endpoint: ${OSS_ENDPOINT:oss-cn-shenzhen.aliyuncs.com}
+  access-key-id: ${ALIBABA_CLOUD_ACCESS_KEY_ID:LTAI5tDwjfteBvivYN41r8sJ}
+  access-key-secret: ${ALIBABA_CLOUD_ACCESS_KEY_SECRET:yowuOGi2nYYnrqGpO3qcz94C4brcPp}
+  bucket-name: ${OSS_BUCKET_NAME:kailin-saas}
   base-path: ${OSS_BASE_PATH:uploads/}
   url-prefix: ${OSS_URL_PREFIX:}
 

+ 2 - 2
zhijiayun-gateway/src/main/resources/application.yml

@@ -69,10 +69,10 @@ sms:
 
 # 阿里云OSS配置
 oss:
-  endpoint: ${OSS_ENDPOINT:oss-cn-hangzhou.aliyuncs.com}
+  endpoint: ${OSS_ENDPOINT:oss-cn-shenzhen.aliyuncs.com}
   access-key-id: ${ALIBABA_CLOUD_ACCESS_KEY_ID:LTAI5tDwjfteBvivYN41r8sJ}
   access-key-secret: ${ALIBABA_CLOUD_ACCESS_KEY_SECRET:yowuOGi2nYYnrqGpO3qcz94C4brcPp}
-  bucket-name: ${OSS_BUCKET_NAME:zhijiayun-pharmacy}
+  bucket-name: ${OSS_BUCKET_NAME:kailin-saas}
   base-path: ${OSS_BASE_PATH:uploads/}
   # 访问URL前缀(如绑定了自定义域名,可覆盖;默认使用OSS公网域名)
   url-prefix: ${OSS_URL_PREFIX:}

+ 103 - 1
zhijiayun-test/src/main/resources/static/activity-test.html

@@ -926,7 +926,45 @@ function renderBusinessLicenseModule() {
          {label:'统一社会信用代码(creditCode)', name:'creditCode', placeholder:'91110000XXXXXXXXXX', default:''},
          {label:'药店名称(pharmacyName)', name:'pharmacyName', placeholder:'XX大药房', default:''},
          {label:'法定代表人(legalPerson,可选)', name:'legalPerson', placeholder:'张三', default:''}
-        ], true, 'businessLicenseInfo', '上传营业执照参与资质审核。审核通过后可获认证标识、解锁邀请有礼奖励、获取比价抽奖资格')}`;
+        ], true, 'businessLicenseInfo', '上传营业执照参与资质审核。审核通过后可获认证标识、解锁邀请有礼奖励、获取比价抽奖资格')}
+    <div class="test-section">
+        <div class="section-title">
+            <span class="method method-post">POST</span>
+            <span class="path">/api/business-license/upload-file</span>
+            <span class="side-tag side-user">用户侧</span>
+            <span class="auth-required auth-tag">需认证</span>
+        </div>
+        <div class="section-desc">选择营业执照图片文件上传至阿里云OSS,返回可公开访问的URL。仅需选择文件即可上传成功,信用代码和药店名称为可选填项。支持 JPG/PNG 格式</div>
+        <details class="field-desc-details">
+            <summary class="field-desc-summary">📞 cURL 调用示例</summary>
+            <pre class="curl-example"># 最简上传(仅图片):
+curl -X POST "\${BASE_URL}/api/business-license/upload-file" \\
+  -H "Authorization: Bearer \${TOKEN}" \\
+  -F "file=@/path/to/license.jpg"
+
+# 完整上传(含执照信息):
+curl -X POST "\${BASE_URL}/api/business-license/upload-file" \\
+  -H "Authorization: Bearer \${TOKEN}" \\
+  -F "file=@/path/to/license.jpg" \\
+  -F "creditCode=91110000XXXXXXXXXX" \\
+  -F "pharmacyName=XX大药房" \\
+  -F "legalPerson=张三"</pre>
+        </details>
+        <div class="form-grid">
+            <div class="form-group"><label>执照图片文件(file) *</label><input type="file" id="business-license-upload-file-file" accept="image/*"></div>
+            <div class="form-group"><label>统一社会信用代码(creditCode,可选)</label><input type="text" id="business-license-upload-file-creditCode" placeholder="91110000XXXXXXXXXX"></div>
+            <div class="form-group"><label>药店名称(pharmacyName,可选)</label><input type="text" id="business-license-upload-file-pharmacyName" placeholder="XX大药房"></div>
+            <div class="form-group"><label>法定代表人(legalPerson,可选)</label><input type="text" id="business-license-upload-file-legalPerson" placeholder="张三"></div>
+        </div>
+        <button class="btn btn-primary" onclick="runBusinessLicenseFileUpload('business-license-upload-file')">执行测试</button>
+        <div class="response-box" id="business-license-upload-file-box" style="display:none;">
+            <div class="response-header">
+                <span class="response-title">响应 <span id="business-license-upload-file-duration" style="color:#999;font-weight:400;"></span></span>
+                <span class="status-badge" id="business-license-upload-file-status"></span>
+            </div>
+            <div class="response-content" id="business-license-upload-file-raw"></div>
+        </div>
+    </div>`;
 }
 
 // ==================== 用户端:我可参加的活动(统一摘要) ====================
@@ -1218,6 +1256,69 @@ function runActivityInviteTests() { runBatchTests('activity-invite'); }
 function runActivityLotteryTests() { runBatchTests('activity-lottery'); }
 function runBusinessLicenseTests() { runBatchTests('business-license'); }
 function runActivityAvailableTests() { runBatchTests('activity-available'); }
+
+// ==================== 文件上传专用测试函数 ====================
+async function runBusinessLicenseFileUpload(sectionId) {
+    const fileInput = document.getElementById(sectionId + '-file');
+    const file = fileInput?.files[0];
+    if (!file) { alert('请先选择营业执照图片文件'); return; }
+
+    const creditCode = document.getElementById(sectionId + '-creditCode')?.value?.trim() || '';
+    const pharmacyName = document.getElementById(sectionId + '-pharmacyName')?.value?.trim() || '';
+    const legalPerson = document.getElementById(sectionId + '-legalPerson')?.value?.trim() || '';
+
+    const formData = new FormData();
+    formData.append('file', file);
+    formData.append('creditCode', creditCode);
+    formData.append('pharmacyName', pharmacyName);
+    if (legalPerson) formData.append('legalPerson', legalPerson);
+
+    const box = document.getElementById(sectionId + '-box');
+    const statusEl = document.getElementById(sectionId + '-status');
+    const rawEl = document.getElementById(sectionId + '-raw');
+    const durEl = document.getElementById(sectionId + '-duration');
+
+    updateBadge(statusEl, 'pending', '上传中...');
+    if (box) box.style.display = 'block';
+
+    if (!stats.hasOwnProperty(sectionId)) {
+        stats.total++;
+        stats.pending = Math.max(0, stats.total - stats.pass - stats.fail);
+        document.getElementById('totalCount').textContent = stats.total;
+        document.getElementById('pendingCount').textContent = stats.pending;
+    }
+
+    const headers = {};
+    if (TOKEN()) headers['Authorization'] = 'Bearer ' + TOKEN();
+
+    const startTime = Date.now();
+    try {
+        const res = await fetch(BASE_URL() + '/api/business-license/upload-file', {
+            method: 'POST',
+            headers,
+            body: formData
+        });
+        const text = await res.text();
+        let data;
+        try { data = safeJsonParse(text); } catch (e) { data = { _raw: text }; }
+        const result = { ok: res.ok, status: res.status, data, duration: Date.now() - startTime };
+
+        if (durEl) durEl.textContent = result.duration + 'ms';
+        if (rawEl) rawEl.textContent = JSON.stringify(result.data, null, 2);
+
+        const passed = validate_businessLicenseUploadFile(result);
+        updateStats(sectionId, passed);
+        updateBadge(statusEl, passed ? 'success' : 'error', passed ? '通过 ' + result.status : '失败 ' + result.status);
+        if (box) box.style.display = 'block';
+    } catch (e) {
+        const dur = Date.now() - startTime;
+        if (durEl) durEl.textContent = dur + 'ms';
+        if (rawEl) rawEl.textContent = JSON.stringify({ error: e.message }, null, 2);
+        updateStats(sectionId, false);
+        updateBadge(statusEl, 'error', '失败 ' + e.message);
+        if (box) box.style.display = 'block';
+    }
+}
 function runActivityRewardsTabTests() { runBatchTests('activity-rewards'); }
 function runAdminCheckinTests() { runBatchTests('admin-checkin'); }
 function runAdminActivityInviteTests() { runBatchTests('admin-activity-invite'); }
@@ -1262,6 +1363,7 @@ function validate_adminActivityLotteryRecords(result) { return result.data?.code
 function validate_adminActivityRewards(result) { return result.data?.code === 200 && result.data?.data?.records !== undefined; }
 function validate_businessLicenseInfo(result) { return result.data?.code === 200 && result.data?.data !== undefined; }
 function validate_businessLicenseUpload(result) { return result.data?.code === 200; }
+function validate_businessLicenseUploadFile(result) { return result.data?.code === 200 && typeof result.data?.data === 'string'; }
 
 // ===================== 初始化 =====================
 window.addEventListener('DOMContentLoaded', () => {

+ 11 - 34
zhijiayun-user/src/main/java/com/xuekairui/user/controller/BusinessLicenseController.java

@@ -3,22 +3,16 @@ package com.xuekairui.user.controller;
 import com.xuekairui.common.Result;
 import com.xuekairui.user.entity.BusinessLicense;
 import com.xuekairui.user.mapper.BusinessLicenseMapper;
+import com.xuekairui.user.service.OssService;
 import jakarta.servlet.http.HttpServletRequest;
 import jakarta.validation.Valid;
 import jakarta.validation.constraints.NotBlank;
 import lombok.Data;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Value;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.multipart.MultipartFile;
 
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.UUID;
-
 /**
  * 营业执照控制器
  */
@@ -29,9 +23,7 @@ import java.util.UUID;
 public class BusinessLicenseController {
 
     private final BusinessLicenseMapper businessLicenseMapper;
-
-    @Value("${upload.path:/opt/uploads}")
-    private String uploadPath;
+    private final OssService ossService;
 
     /**
      * 上传/更新营业执照(JSON 方式,传图片URL)
@@ -49,44 +41,29 @@ public class BusinessLicenseController {
     }
 
     /**
-     * 上传/更新营业执照(文件方式,multipart)
+     * 上传/更新营业执照(文件方式,multipart → OSS
      * POST /api/business-license/upload-file
      * Content-Type: multipart/form-data
-     * Fields: file(required), creditCode(required), pharmacyName(required), legalPerson(optional)
+     * Fields: file(required), creditCode(optional), pharmacyName(optional), legalPerson(optional)
      */
     @PostMapping("/upload-file")
-    public Result<Void> uploadFile(
+    public Result<String> uploadFile(
             @RequestParam("file") MultipartFile file,
-            @RequestParam("creditCode") String creditCode,
-            @RequestParam("pharmacyName") String pharmacyName,
+            @RequestParam(value = "creditCode", required = false) String creditCode,
+            @RequestParam(value = "pharmacyName", required = false) String pharmacyName,
             @RequestParam(value = "legalPerson", required = false) String legalPerson,
-            HttpServletRequest httpRequest) throws IOException {
+            HttpServletRequest httpRequest) {
 
         if (file.isEmpty()) {
             return Result.error(400, "文件不能为空");
         }
         Long userId = (Long) httpRequest.getAttribute("userId");
 
-        // 确保目录存在
-        Path dir = Paths.get(uploadPath);
-        if (!Files.exists(dir)) {
-            Files.createDirectories(dir);
-            log.info("创建上传目录: {}", dir.toAbsolutePath());
-        }
+        String fileUrl = ossService.upload(file);
+        log.info("营业执照文件上传至OSS成功: {}", fileUrl);
 
-        // 生成唯一文件名
-        String originalFilename = file.getOriginalFilename();
-        String extension = (originalFilename != null && originalFilename.contains("."))
-                ? originalFilename.substring(originalFilename.lastIndexOf(".")) : "";
-        String fileName = UUID.randomUUID().toString().replace("-", "") + extension;
-
-        Path targetPath = dir.resolve(fileName);
-        file.transferTo(targetPath.toFile());
-        log.info("营业执照文件上传成功: {}", targetPath.toAbsolutePath());
-
-        String fileUrl = uploadPath + "/" + fileName;
         saveLicense(userId, fileUrl, creditCode, pharmacyName, legalPerson);
-        return Result.success();
+        return Result.success(fileUrl);
     }
 
     /**