| @@ -22,6 +22,7 @@ public class UpdateUserReq { | |||||
| private LocalDate expiryDate; | private LocalDate expiryDate; | ||||
| private String locale; | private String locale; | ||||
| private String remarks; | private String remarks; | ||||
| private String staffNo; | |||||
| // @NotNull | // @NotNull | ||||
| @@ -126,4 +127,12 @@ public class UpdateUserReq { | |||||
| this.remarks = remarks; | this.remarks = remarks; | ||||
| } | } | ||||
| public String getStaffNo() { // Add getter | |||||
| return staffNo; | |||||
| } | |||||
| public void setStaffNo(String staffNo) { // Add setter | |||||
| this.staffNo = staffNo; | |||||
| } | |||||
| } | } | ||||
| @@ -0,0 +1,76 @@ | |||||
| package com.ffii.fpsms.modules.user.service | |||||
| import com.ffii.core.utils.PdfUtils | |||||
| import com.ffii.core.utils.QrCodeUtil | |||||
| import com.ffii.fpsms.modules.user.entity.UserRepository | |||||
| import com.ffii.fpsms.modules.user.web.ExportUserQrCodeRequest | |||||
| import net.sf.jasperreports.engine.JasperCompileManager | |||||
| import net.sf.jasperreports.engine.JasperPrint | |||||
| import net.sf.jasperreports.engine.export.JRPdfExporter | |||||
| import net.sf.jasperreports.export.SimpleExporterInput | |||||
| import net.sf.jasperreports.export.SimpleOutputStreamExporterOutput | |||||
| import org.springframework.core.io.ClassPathResource | |||||
| import org.springframework.stereotype.Service | |||||
| import java.io.FileNotFoundException | |||||
| import java.awt.GraphicsEnvironment | |||||
| import kotlinx.serialization.json.Json | |||||
| import kotlinx.serialization.encodeToString | |||||
| @Service | |||||
| class UserQrCodeService( | |||||
| private val userRepository: UserRepository | |||||
| ) { | |||||
| fun exportUserQrCode(request: ExportUserQrCodeRequest): Map<String, Any> { | |||||
| val QRCODE_HANDLE_PDF = "qrCodeHandle/qrCodeHandle.jrxml" | |||||
| val resource = ClassPathResource(QRCODE_HANDLE_PDF) | |||||
| if (!resource.exists()) { | |||||
| throw FileNotFoundException("Report file not found: $QRCODE_HANDLE_PDF") | |||||
| } | |||||
| val inputStream = resource.inputStream | |||||
| val qrCodeHandleReport = JasperCompileManager.compileReport(inputStream) | |||||
| val users = userRepository.findAllById(request.userIds) | |||||
| val fields = mutableListOf<MutableMap<String, Any>>() | |||||
| for (user in users) { | |||||
| val field = mutableMapOf<String, Any>() | |||||
| val staffNo = user.staffNo ?: "" | |||||
| val username = user.username ?: "N/A" | |||||
| val qrContentMap = mapOf("staffNo" to staffNo) | |||||
| val qrCodeContent = Json.encodeToString(qrContentMap) | |||||
| val qrCodeImage = QrCodeUtil.generateQRCodeImage(qrCodeContent) | |||||
| field["username"] = username | |||||
| field["staffNo"] = staffNo.ifEmpty { "N/A" } | |||||
| field["qrCode"] = qrCodeImage | |||||
| fields.add(field) | |||||
| } | |||||
| val params: MutableMap<String, Any> = mutableMapOf() | |||||
| // Configure for Chinese character support | |||||
| // Try to find a Chinese-supporting font | |||||
| val availableFonts = GraphicsEnvironment.getLocalGraphicsEnvironment().availableFontFamilyNames | |||||
| val chineseFont = availableFonts.find { | |||||
| it.contains("SimSun", ignoreCase = true) || | |||||
| it.contains("Microsoft YaHei", ignoreCase = true) || | |||||
| it.contains("STSong", ignoreCase = true) || | |||||
| it.contains("SimHei", ignoreCase = true) | |||||
| } ?: "Arial Unicode MS" // Fallback | |||||
| params["net.sf.jasperreports.default.pdf.encoding"] = "Identity-H" | |||||
| params["net.sf.jasperreports.default.pdf.embedded"] = true | |||||
| params["net.sf.jasperreports.default.pdf.font.name"] = chineseFont | |||||
| return mapOf( | |||||
| "report" to PdfUtils.fillReport(qrCodeHandleReport, fields, params), | |||||
| "fileName" to (users.firstOrNull()?.username ?: "user_qrcode") | |||||
| ) | |||||
| } | |||||
| } | |||||
| @@ -111,7 +111,8 @@ public class UserService extends AbstractBaseEntityService<User, Long, UserRepos | |||||
| + " u.email," | + " u.email," | ||||
| + " u.phone1," | + " u.phone1," | ||||
| + " u.phone2," | + " u.phone2," | ||||
| + " u.remarks " | |||||
| + " u.remarks," | |||||
| + " u.staffNo" | |||||
| + " FROM `user` u" | + " FROM `user` u" | ||||
| + " left join user_group ug on u.id = ug.userId" | + " left join user_group ug on u.id = ug.userId" | ||||
| + " where u.deleted = false"); | + " where u.deleted = false"); | ||||
| @@ -24,6 +24,7 @@ public class UserRecord { | |||||
| private String phone1; | private String phone1; | ||||
| private String phone2; | private String phone2; | ||||
| private String remarks; | private String remarks; | ||||
| private String staffNo; | |||||
| public Integer getId() { | public Integer getId() { | ||||
| return id; | return id; | ||||
| @@ -151,5 +152,6 @@ public class UserRecord { | |||||
| public void setRemarks(String remarks) { | public void setRemarks(String remarks) { | ||||
| this.remarks = remarks; | this.remarks = remarks; | ||||
| } | } | ||||
| public String getStaffNo() { return staffNo; } | |||||
| public void setStaffNo(String staffNo) { this.staffNo = staffNo; } | |||||
| } | } | ||||
| @@ -0,0 +1,5 @@ | |||||
| package com.ffii.fpsms.modules.user.web | |||||
| data class ExportUserQrCodeRequest( | |||||
| val userIds: List<Long> | |||||
| ) | |||||
| @@ -42,6 +42,14 @@ import com.ffii.fpsms.modules.user.req.UpdateUserReq; | |||||
| import com.ffii.fpsms.modules.user.service.UserService; | import com.ffii.fpsms.modules.user.service.UserService; | ||||
| import com.ffii.fpsms.modules.user.service.res.LoadUserRes; | import com.ffii.fpsms.modules.user.service.res.LoadUserRes; | ||||
| import com.ffii.fpsms.modules.user.web.ExportUserQrCodeRequest; | |||||
| import com.ffii.fpsms.modules.user.service.UserQrCodeService; | |||||
| import jakarta.servlet.http.HttpServletResponse; | |||||
| import net.sf.jasperreports.engine.JasperExportManager; | |||||
| import net.sf.jasperreports.engine.JasperPrint; | |||||
| import java.io.OutputStream; | |||||
| import java.io.UnsupportedEncodingException; | |||||
| import jakarta.validation.Valid; | import jakarta.validation.Valid; | ||||
| import jakarta.validation.constraints.NotBlank; | import jakarta.validation.constraints.NotBlank; | ||||
| @@ -53,14 +61,17 @@ public class UserController{ | |||||
| private UserService userService; | private UserService userService; | ||||
| private PasswordEncoder passwordEncoder; | private PasswordEncoder passwordEncoder; | ||||
| private SettingsService settingsService; | private SettingsService settingsService; | ||||
| private UserQrCodeService userQrCodeService; | |||||
| public UserController( | public UserController( | ||||
| UserService userService, | UserService userService, | ||||
| PasswordEncoder passwordEncoder, | PasswordEncoder passwordEncoder, | ||||
| SettingsService settingsService) { | |||||
| SettingsService settingsService, | |||||
| UserQrCodeService userQrCodeService) { | |||||
| this.userService = userService; | this.userService = userService; | ||||
| this.passwordEncoder = passwordEncoder; | this.passwordEncoder = passwordEncoder; | ||||
| this.settingsService = settingsService; | this.settingsService = settingsService; | ||||
| this.userQrCodeService = userQrCodeService; | |||||
| } | } | ||||
| // @Operation(summary = "list user", responses = { @ApiResponse(responseCode = "200"), | // @Operation(summary = "list user", responses = { @ApiResponse(responseCode = "200"), | ||||
| @@ -229,6 +240,20 @@ public class UserController{ | |||||
| return new PasswordRule(settingsService); | return new PasswordRule(settingsService); | ||||
| } | } | ||||
| @PostMapping("/export-qrcode") | |||||
| public void exportQrCode(@Valid @RequestBody ExportUserQrCodeRequest request, HttpServletResponse response) | |||||
| throws Exception { | |||||
| response.setCharacterEncoding("utf-8"); | |||||
| response.setContentType("application/pdf"); | |||||
| OutputStream out = response.getOutputStream(); | |||||
| Map<String, Object> pdf = userQrCodeService.exportUserQrCode(request); | |||||
| JasperPrint jasperPrint = (JasperPrint) pdf.get("report"); | |||||
| String fileName = (String) pdf.get("fileName"); | |||||
| response.addHeader("Content-Disposition", "attachment; filename=\"" + fileName + "_qrcode.pdf\""); | |||||
| out.write(JasperExportManager.exportReportToPdf(jasperPrint)); | |||||
| out.flush(); | |||||
| } | |||||
| public static class AdminChangePwdReq { | public static class AdminChangePwdReq { | ||||
| private Long id; | private Long id; | ||||
| @NotBlank | @NotBlank | ||||
| @@ -0,0 +1,42 @@ | |||||
| <?xml version="1.0" encoding="UTF-8"?> | |||||
| <!-- Created with Jaspersoft Studio version 6.21.3.final using JasperReports Library version 6.21.3-4a3078d20785ebe464f18037d738d12fc98c13cf --> | |||||
| <jasperReport xmlns="http://jasperreports.sourceforge.net/jasperreports" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://jasperreports.sourceforge.net/jasperreports http://jasperreports.sourceforge.net/xsd/jasperreport.xsd" name="qrCodeHandle" pageWidth="595" pageHeight="842" columnWidth="555" leftMargin="20" rightMargin="20" topMargin="80" bottomMargin="20" uuid="c2f7cd27-3725-47ce-ac85-d8a38dc906fa"> | |||||
| <property name="com.jaspersoft.studio.data.defaultdataadapter" value="One Empty Record"/> | |||||
| <property name="com.jaspersoft.studio.unit." value="pixel"/> | |||||
| <property name="com.jaspersoft.studio.unit.pageHeight" value="pixel"/> | |||||
| <property name="com.jaspersoft.studio.unit.pageWidth" value="pixel"/> | |||||
| <property name="com.jaspersoft.studio.unit.topMargin" value="pixel"/> | |||||
| <property name="com.jaspersoft.studio.unit.bottomMargin" value="pixel"/> | |||||
| <property name="com.jaspersoft.studio.unit.leftMargin" value="pixel"/> | |||||
| <property name="com.jaspersoft.studio.unit.rightMargin" value="pixel"/> | |||||
| <property name="com.jaspersoft.studio.unit.columnWidth" value="pixel"/> | |||||
| <property name="com.jaspersoft.studio.unit.columnSpacing" value="pixel"/> | |||||
| <queryString> | |||||
| <![CDATA[]]> | |||||
| </queryString> | |||||
| <field name="username" class="java.lang.String"/> | |||||
| <field name="staffNo" class="java.lang.String"/> | |||||
| <field name="qrCode" class="java.awt.Image"/> | |||||
| <background> | |||||
| <band splitType="Stretch"/> | |||||
| </background> | |||||
| <detail> | |||||
| <band height="670" splitType="Stretch"> | |||||
| <textField isStretchWithOverflow="true" isBlankWhenNull="false"> | |||||
| <reportElement stretchType="RelativeToTallestObject" x="37" y="0" width="480" height="120" uuid="e3faf8de-84ba-423f-b6cf-84ba21598686"> | |||||
| <property name="com.jaspersoft.studio.unit.x" value="px"/> | |||||
| <property name="com.jaspersoft.studio.unit.width" value="px"/> | |||||
| <property name="com.jaspersoft.studio.unit.height" value="px"/> | |||||
| </reportElement> | |||||
| <textElement textAlignment="Center" verticalAlignment="Middle"> | |||||
| <font size="54" isBold="true" fontName="微軟正黑體" pdfEncoding="Identity-H" isPdfEmbedded="true"/> | |||||
| </textElement> | |||||
| <textFieldExpression><![CDATA[$F{username}]]></textFieldExpression> | |||||
| </textField> | |||||
| <image> | |||||
| <reportElement x="27" y="120" width="500" height="500" uuid="b1a8ee23-9f0f-4014-9996-e0025222dcd2"/> | |||||
| <imageExpression><![CDATA[$F{qrCode}]]></imageExpression> | |||||
| </image> | |||||
| </band> | |||||
| </detail> | |||||
| </jasperReport> | |||||