陈昶聿
2 天以前 e32414ce775fefdd0602ac96c218c4f0758f7033
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
package com.ruoyi.web.controller.smartor;
 
import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.WriterException;
import com.google.zxing.client.j2se.MatrixToImageWriter;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.QRCodeWriter;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import com.mchange.v2.uid.UidUtils;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.framework.config.ServerConfig;
import com.smartor.domain.Question;
import com.smartor.domain.SvyLibTemplate;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
 
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.net.URI;
import java.util.*;
 
@Slf4j
@Api(description = "问卷二维码")
@RestController
@RequestMapping("/qrcode")
public class QRCodeController {
 
    private final Configuration configuration;
 
    public QRCodeController(Configuration configuration) {
        this.configuration = configuration;
    }
 
    @Value("${ruoyi.profile}")
    private String profile;
 
    /**
     * 公司logo在classpath中的路径,打包进jar后任何环境都能读取到
     */
    private static final String LOGO_CLASSPATH = "/images/hrslogo.png";
 
    @Autowired
    private ServerConfig serverConfig;
 
    /**
     * 问卷题目
     *
     * @param reqid 问卷ID
     *              userid  用户ID
     * @return
     * @throws IOException
     * @throws TemplateException
     */
    @ResponseBody
    @GetMapping(value = "/generateStaticHtml/{reqid}/{userid}", produces = MediaType.TEXT_HTML_VALUE)
    public String generateStaticHtml(@PathVariable("reqid") String reqid, @PathVariable("userid") String userid) throws IOException, TemplateException {
        // 创建Freemarker配置
        Configuration configuration = new Configuration(Configuration.VERSION_2_3_31);
        configuration.setClassForTemplateLoading(SvyLibTemplateController.class, "/template");
        configuration.setDefaultEncoding("UTF-8");
 
        try {
            // 加载模板
            Template template = configuration.getTemplate("question.ftl");
 
            // 准备题目数据
            List<Question> questions = new ArrayList<>();
 
            // 单选题
            List<String> singleChoiceOptions = new ArrayList<>();
            singleChoiceOptions.add("Option 1");
            singleChoiceOptions.add("Option 2");
            singleChoiceOptions.add("Option 3");
            Question singleChoiceQuestion = new Question("singleChoice", "单选题111", "单选题题目1:", singleChoiceOptions);
 
            List<String> singleChoiceOptions2 = new ArrayList<>();
            singleChoiceOptions2.add("Option 4");
            singleChoiceOptions2.add("Option 5");
            singleChoiceOptions2.add("Option 6");
            Question singleChoiceQuestion2 = new Question("singleChoice", "单选题222", "单选题题目2:", singleChoiceOptions2);
 
            questions.add(singleChoiceQuestion);
            questions.add(singleChoiceQuestion2);
 
            // 多选题
            List<String> multipleChoiceOptions = new ArrayList<>();
            multipleChoiceOptions.add("Option A");
            multipleChoiceOptions.add("Option B");
            multipleChoiceOptions.add("Option C");
            Question multipleChoiceQuestion = new Question("multipleChoice", "多选题1", "Question 2: Select multiple options", multipleChoiceOptions);
            questions.add(multipleChoiceQuestion);
 
            // 问答题
            Question openEndedQuestion = new Question("openEnded", "问答题题", "问答题题目1", null);
            questions.add(openEndedQuestion);
 
            // 准备模板数据
            Map<String, Object> data = new HashMap<>();
            data.put("questions", questions);
 
            // 渲染模板
            StringWriter writer = new StringWriter();
            template.process(data, writer);
            String renderedTemplate = writer.toString();
            return writer.toString();
            // 返回模板内容给客户端
            //  System.out.println(renderedTemplate);  // 这里只是示例,实际应用中应将内容返回给客户端进行展示
        } catch (IOException | TemplateException e) {
            e.printStackTrace();
        }
        return null;
    }
 
    /**
     * @param url 问卷ID
     * @param url 用户ID
     */
    @ApiOperation("获取问卷二维码")
    @PostMapping(value = "/getQRcode")
    public AjaxResult getQRcode(@RequestParam("url") String url) {
        log.info("获取问卷二维码的入参:{}", url);
        // 尝试从url中提取p参数的值作为图片名称,值为空则用UUID
        String fileName = UUID.randomUUID().toString().replace("-", "");
        try {
            URI uri = new URI(url);
            String query = uri.getQuery();
            if (query != null) {
                for (String param : query.split("&")) {
                    String[] pair = param.split("=", 2);
                    if ("p".equals(pair[0]) && pair.length > 1 && !pair[1].isEmpty()) {
                        fileName = pair[1] + "-" + System.currentTimeMillis();
                        break;
                    }
                }
            }
        } catch (Exception e) {
            log.warn("解析url中的p参数失败,使用UUID作为文件名");
        }
        String filePath = profile + "\\qrpath" + "\\" + fileName + ".png"; // 保存二维码图像的文件路径
        //判断文件夹是否存在
        File file = new File(profile + "\\qrpath");
        if (!file.exists()) {
            file.mkdirs();
        }
        generateQRCode(url, filePath);
        return AjaxResult.success(serverConfig.getUrl() + "/profile/qrpath/" + fileName + ".png");
    }
 
    private void generateQRCode(String url, String filePath) {
        QRCodeWriter qrCodeWriter = new QRCodeWriter();
        try {
            // 使用高容错级别(H级,30%容错),确保二维码中间嵌入logo后仍可正常扫描
            Map<EncodeHintType, Object> hints = new HashMap<>();
            hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
            hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
            hints.put(EncodeHintType.MARGIN, 1);
 
            BitMatrix bitMatrix = qrCodeWriter.encode(url, BarcodeFormat.QR_CODE, 500, 500, hints);
 
            // 将BitMatrix转为二值BufferedImage(黑白)
            BufferedImage binaryImage = MatrixToImageWriter.toBufferedImage(bitMatrix);
 
            // 创建RGB彩色图像,把二维码画上去,否则后续绘制logo会丢失颜色
            BufferedImage qrImage = new BufferedImage(500, 500, BufferedImage.TYPE_INT_RGB);
            Graphics2D graphics = qrImage.createGraphics();
            graphics.setColor(Color.WHITE);
            graphics.fillRect(0, 0, 500, 500);
            graphics.drawImage(binaryImage, 0, 0, null);
 
            // 从classpath读取logo并绘制到二维码中间
            try (InputStream logoStream = this.getClass().getResourceAsStream(LOGO_CLASSPATH)) {
                if (logoStream != null) {
                    BufferedImage logoImage = ImageIO.read(logoStream);
 
                    // 计算logo尺寸,设为二维码的1/5
                    int logoWidth = qrImage.getWidth() / 5;
                    int logoHeight = qrImage.getHeight() / 5;
 
                    // 计算logo居中位置
                    int logoX = (qrImage.getWidth() - logoWidth) / 2;
                    int logoY = (qrImage.getHeight() - logoHeight) / 2;
 
                    // 绘制白色圆角背景,让logo更突出
                    int padding = 6;
                    graphics.setColor(Color.WHITE);
                    graphics.fillRoundRect(logoX - padding, logoY - padding, logoWidth + padding * 2, logoHeight + padding * 2, 10, 10);
 
                    // 绘制logo
                    graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
                    graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
                    graphics.drawImage(logoImage, logoX, logoY, logoWidth, logoHeight, null);
                } else {
                    log.warn("classpath中未找到二维码logo: {}", LOGO_CLASSPATH);
                }
            }
            graphics.dispose();
 
            // 输出最终图片
            File outputFile = new File(filePath);
            ImageIO.write(qrImage, "PNG", outputFile);
        } catch (WriterException | IOException e) {
            e.printStackTrace();
        }
    }
 
    /**
     * 问券结果处理
     *
     * @param request
     */
    @PostMapping(value = "/getFormDate")
    public void getFormDate(HttpServletRequest request) {
        Map<String, String[]> formData = request.getParameterMap();
        System.out.println(formData.toString());
    }
 
}