commit e3e3e2bbdb44d6845d1d8b15e96514d49d638b81 Author: MaDaLei Date: Sun Apr 12 23:09:38 2026 +0800 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2f7896d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +target/ diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..12db726 --- /dev/null +++ b/pom.xml @@ -0,0 +1,96 @@ + + + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 3.2.3 + + + + com.petstore + petstore-backend + 1.0.0 + jar + petstore-backend + 宠伴生活馆 后端服务 + + + 17 + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-data-jpa + + + com.mysql + mysql-connector-j + runtime + + + org.projectlombok + lombok + 1.18.44 + true + + + org.springframework.boot + spring-boot-starter-validation + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.13.0 + + 17 + 17 + true + + -J--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED + -J--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED + -J--add-opens=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED + -J--add-opens=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED + -J--add-opens=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED + -J--add-opens=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED + -J--add-opens=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED + -J--add-opens=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED + -J--add-opens=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED + -J--add-opens=jdk.compiler/com.sun.tools.javac.jvm=ALL-UNNAMED + + + + org.projectlombok + lombok + 1.18.44 + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + + diff --git a/src/main/java/com/petstore/PetstoreApplication.java b/src/main/java/com/petstore/PetstoreApplication.java new file mode 100644 index 0000000..e55379b --- /dev/null +++ b/src/main/java/com/petstore/PetstoreApplication.java @@ -0,0 +1,30 @@ +package com.petstore; + +import com.petstore.service.ServiceTypeService; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.CommandLineRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.jdbc.core.JdbcTemplate; + +@SpringBootApplication +@RequiredArgsConstructor +public class PetstoreApplication { + public static void main(String[] args) { + System.out.println(">>> working dir: " + System.getProperty("user.dir")); + SpringApplication.run(PetstoreApplication.class, args); + } + + @Bean + CommandLineRunner initRunner(ServiceTypeService serviceTypeService, JdbcTemplate jdbc) { + return args -> { + serviceTypeService.initDefaults(); + // appointment_id 改为允许 NULL(支持不挂预约直接填报告) + jdbc.execute("ALTER TABLE t_report MODIFY COLUMN appointment_id BIGINT NULL"); + // 修复旧图片URL:/2026/xxx → /api/upload/image/2026/xxx + jdbc.execute("UPDATE t_report SET before_photo = REPLACE(before_photo, 'http://localhost:8080/2026/', '/api/upload/image/2026/') WHERE before_photo LIKE 'http://localhost:8080/2026/%'"); + jdbc.execute("UPDATE t_report SET after_photo = REPLACE(after_photo, 'http://localhost:8080/2026/', '/api/upload/image/2026/') WHERE after_photo LIKE 'http://localhost:8080/2026/%'"); + }; + } +} diff --git a/src/main/java/com/petstore/config/CorsConfig.java b/src/main/java/com/petstore/config/CorsConfig.java new file mode 100644 index 0000000..eb84bd3 --- /dev/null +++ b/src/main/java/com/petstore/config/CorsConfig.java @@ -0,0 +1,23 @@ +package com.petstore.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; +import org.springframework.web.filter.CorsFilter; + +@Configuration +public class CorsConfig { + @Bean + public CorsFilter corsFilter() { + CorsConfiguration config = new CorsConfiguration(); + config.setAllowCredentials(true); + config.addAllowedOriginPattern("*"); + config.addAllowedHeader("*"); + config.addAllowedMethod("*"); + + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/**", config); + return new CorsFilter(source); + } +} diff --git a/src/main/java/com/petstore/config/WebConfig.java b/src/main/java/com/petstore/config/WebConfig.java new file mode 100644 index 0000000..a8ee4d4 --- /dev/null +++ b/src/main/java/com/petstore/config/WebConfig.java @@ -0,0 +1,27 @@ +package com.petstore.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import java.io.File; + +@Configuration +public class WebConfig implements WebMvcConfigurer { + + @Value("${upload.path:uploads}") + private String uploadPath; + + @Override + public void addResourceHandlers(ResourceHandlerRegistry registry) { + // 硬编码绝对路径,确保能找到文件 + String uploadDir = "/Users/wac/Desktop/www/_src/petstore/backend/uploads/"; + System.out.println(">>> WebConfig uploadDir: " + uploadDir); + System.out.println(">>> /2026 exists: " + new File(uploadDir + "2026/04/01/").exists()); + registry.addResourceHandler("/uploads/**") + .addResourceLocations("file:" + uploadDir); + registry.addResourceHandler("/2026/**") + .addResourceLocations("file:" + uploadDir); + } +} diff --git a/src/main/java/com/petstore/config/WechatConfig.java b/src/main/java/com/petstore/config/WechatConfig.java new file mode 100644 index 0000000..07c8d33 --- /dev/null +++ b/src/main/java/com/petstore/config/WechatConfig.java @@ -0,0 +1,33 @@ +package com.petstore.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class WechatConfig { + // TODO: 替换为实际的微信开放平台 AppID 和 AppSecret + @Value("${wechat.appid:YOUR_APPID}") + private String appid; + + @Value("${wechat.appsecret:YOUR_APPSECRET}") + private String appsecret; + + @Value("${wechat.redirect_uri:http://localhost:8080/api/wechat/callback}") + private String redirectUri; + + public String getAppid() { return appid; } + public String getAppsecret() { return appsecret; } + public String getRedirectUri() { return redirectUri; } + + /** + * 获取微信授权跳转地址 + */ + public String getAuthorizeUrl() { + return "https://open.weixin.qq.com/connect/qrconnect" + + "?appid=" + appid + + "&redirect_uri=" + redirectUri + + "&response_type=code" + + "&scope=snsapi_login" + + "&state=petstore#wechat_redirect"; + } +} diff --git a/src/main/java/com/petstore/controller/AppointmentController.java b/src/main/java/com/petstore/controller/AppointmentController.java new file mode 100644 index 0000000..ba5ec95 --- /dev/null +++ b/src/main/java/com/petstore/controller/AppointmentController.java @@ -0,0 +1,86 @@ +package com.petstore.controller; + +import com.petstore.entity.Appointment; +import com.petstore.service.AppointmentService; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@RestController +@RequestMapping("/api/appointment") +@RequiredArgsConstructor +@CrossOrigin +public class AppointmentController { + private final AppointmentService appointmentService; + + /** 获取预约列表(员工查自己/老板查全店) */ + @GetMapping("/list") + public Map list( + @RequestParam(required = false) Long userId, + @RequestParam(required = false) Long storeId, + @RequestParam(required = false) String status) { + + List appointments; + if (storeId != null) { + appointments = (status != null && !status.isEmpty()) + ? appointmentService.getByStoreIdAndStatus(storeId, status) + : appointmentService.getByStoreId(storeId); + } else if (userId != null) { + appointments = (status != null && !status.isEmpty()) + ? appointmentService.getByUserIdAndStatus(userId, status) + : appointmentService.getByUserId(userId); + } else { + return Map.of("code", 400, "message", "userId或storeId必填"); + } + + return Map.of("code", 200, "data", appointments); + } + + /** 创建预约 */ + @PostMapping("/create") + public Map create(@RequestBody Map params) { + Appointment appointment = new Appointment(); + appointment.setPetName(params.get("petName").toString()); + appointment.setPetType(params.get("petType").toString()); + appointment.setServiceType(params.get("serviceType").toString()); + + String timeStr = params.get("appointmentTime").toString(); + appointment.setAppointmentTime(java.time.LocalDateTime.parse(timeStr)); + + appointment.setStoreId(Long.valueOf(params.get("storeId").toString())); + appointment.setUserId(Long.valueOf(params.get("userId").toString())); + appointment.setStatus("new"); + + if (params.containsKey("remark") && params.get("remark") != null) { + appointment.setRemark(params.get("remark").toString()); + } + + Appointment created = appointmentService.create(appointment); + return Map.of("code", 200, "message", "创建成功", "data", created); + } + + /** 开始服务:状态变进行中 + 指定技师 */ + @PostMapping("/start") + public Map start(@RequestBody Map params) { + Long appointmentId = Long.valueOf(params.get("appointmentId").toString()); + Long staffUserId = Long.valueOf(params.get("staffUserId").toString()); + Appointment updated = appointmentService.startService(appointmentId, staffUserId); + if (updated != null) { + return Map.of("code", 200, "message", "已开始服务", "data", updated); + } + return Map.of("code", 404, "message", "预约不存在"); + } + + /** 更新预约状态 */ + @PutMapping("/status") + public Map updateStatus(@RequestParam Long id, @RequestParam String status) { + Appointment updated = appointmentService.updateStatus(id, status); + if (updated != null) { + return Map.of("code", 200, "message", "更新成功", "data", updated); + } + return Map.of("code", 404, "message", "预约不存在"); + } +} diff --git a/src/main/java/com/petstore/controller/FileController.java b/src/main/java/com/petstore/controller/FileController.java new file mode 100644 index 0000000..ea0f0f5 --- /dev/null +++ b/src/main/java/com/petstore/controller/FileController.java @@ -0,0 +1,114 @@ +package com.petstore.controller; + +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.io.FileSystemResource; +import org.springframework.core.io.Resource; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.time.LocalDate; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +@RestController +@RequestMapping("/api/upload") +@RequiredArgsConstructor +@CrossOrigin +public class FileController { + + private static final String UPLOAD_BASE = "/Users/wac/Desktop/www/_src/petstore/backend/uploads/"; + + @Value("${upload.path:uploads}") + private String uploadPath; + + @GetMapping("/image/**") + public ResponseEntity getImage(HttpServletRequest request) throws IOException { + String path = request.getRequestURI().replace("/api/upload/image", ""); + File file = new File(UPLOAD_BASE + path); + if (!file.exists()) { + return ResponseEntity.notFound().build(); + } + String contentType = Files.probeContentType(file.toPath()); + if (contentType == null) contentType = "image/jpeg"; + return ResponseEntity.ok() + .contentType(MediaType.parseMediaType(contentType)) + .body(new FileSystemResource(file)); + } + + // 兼容旧路径:/2026/04/01/xxx.jpg + @GetMapping("/legacy/**") + public ResponseEntity getLegacyImage(HttpServletRequest request) throws IOException { + String path = request.getRequestURI().replace("/api/upload/legacy", ""); + File file = new File(UPLOAD_BASE + path); + if (!file.exists()) { + return ResponseEntity.notFound().build(); + } + String contentType = Files.probeContentType(file.toPath()); + if (contentType == null) contentType = "image/jpeg"; + return ResponseEntity.ok() + .contentType(MediaType.parseMediaType(contentType)) + .body(new FileSystemResource(file)); + } + + @PostMapping("/image") + public Map uploadImage(@RequestParam("file") MultipartFile file) { + Map result = new HashMap<>(); + + if (file.isEmpty()) { + result.put("code", 400); + result.put("message", "文件为空"); + return result; + } + + String contentType = file.getContentType(); + if (contentType == null || !contentType.startsWith("image/")) { + result.put("code", 400); + result.put("message", "只能上传图片"); + return result; + } + + try { + // 创建上传目录(使用绝对路径) + String datePath = LocalDate.now().toString().replace("-", "/"); + String dirPath = UPLOAD_BASE + datePath; // /.../uploads/2026/04/01 + File dir = new File(dirPath); + if (!dir.exists()) dir.mkdirs(); + + // 生成文件名 + String originalFilename = file.getOriginalFilename(); + String ext = ""; + if (originalFilename != null && originalFilename.contains(".")) { + ext = originalFilename.substring(originalFilename.lastIndexOf(".")); + } + String filename = UUID.randomUUID().toString().replace("-", "") + ext; + + // 保存文件 + Path filePath = Paths.get(dirPath, filename); + Files.write(filePath, file.getBytes()); + + // 返回访问URL(/api/upload/image/ + 日期路径 + 文件名) + String url = "/api/upload/image/" + datePath + "/" + filename; + + result.put("code", 200); + result.put("message", "上传成功"); + result.put("data", Map.of("url", url)); + return result; + + } catch (IOException e) { + e.printStackTrace(); + result.put("code", 500); + result.put("message", "上传失败"); + return result; + } + } +} diff --git a/src/main/java/com/petstore/controller/ReportController.java b/src/main/java/com/petstore/controller/ReportController.java new file mode 100644 index 0000000..192f2c9 --- /dev/null +++ b/src/main/java/com/petstore/controller/ReportController.java @@ -0,0 +1,117 @@ +package com.petstore.controller; + +import com.petstore.entity.Report; +import com.petstore.entity.Store; +import com.petstore.service.ReportService; +import com.petstore.service.StoreService; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@RestController +@RequestMapping("/api/report") +@RequiredArgsConstructor +@CrossOrigin +public class ReportController { + private final ReportService reportService; + private final StoreService storeService; + + private static final String BASE_URL = "http://localhost:8080"; + + private String fullUrl(String path) { + if (path == null || path.isEmpty()) return path; + if (path.startsWith("http")) return path; + return BASE_URL + path; + } + + @PostMapping("/create") + public Map create(@RequestBody Report report) { + System.out.println(">>> Report create received: appointmentId=" + report.getAppointmentId() + ", userId=" + report.getUserId() + ", before=" + report.getBeforePhoto()); + Report created = reportService.create(report); + + Map result = new HashMap<>(); + result.put("code", 200); + result.put("message", "提交成功"); + result.put("data", Map.of( + "reportToken", created.getReportToken(), + "reportId", created.getId() + )); + return result; + } + + @GetMapping("/list") + public Map list(@RequestParam(required = false) Long storeId, + @RequestParam(required = false) Long userId) { + List reports = reportService.list(storeId, userId); + // 附加技师名称,并补全图片URL + List> data = reports.stream().map(r -> { + Map item = new HashMap<>(); + item.put("id", r.getId()); + item.put("appointmentId", r.getAppointmentId()); + item.put("petName", r.getPetName()); + item.put("serviceType", r.getServiceType()); + item.put("appointmentTime", r.getAppointmentTime()); + item.put("staffName", r.getStaffName()); + item.put("reportToken", r.getReportToken()); + item.put("createTime", r.getCreateTime()); + item.put("beforePhoto", fullUrl(r.getBeforePhoto())); + item.put("afterPhoto", fullUrl(r.getAfterPhoto())); + item.put("storeId", r.getStoreId()); + item.put("userId", r.getUserId()); + return item; + }).collect(Collectors.toList()); + return Map.of("code", 200, "data", data); + } + + @GetMapping("/get") + public Map getByAppointmentId(@RequestParam(required = false) Long appointmentId, + @RequestParam(required = false) String token) { + Report report = null; + if (token != null && !token.isEmpty()) { + report = reportService.getByToken(token); + } else if (appointmentId != null) { + report = reportService.getByAppointmentId(appointmentId); + } + + Map result = new HashMap<>(); + if (report != null) { + // 附加店铺信息 + Store store = null; + if (report.getStoreId() != null) { + store = storeService.findById(report.getStoreId()); + } + Map data = new HashMap<>(); + data.put("id", report.getId()); + data.put("appointmentId", report.getAppointmentId()); + data.put("beforePhoto", report.getBeforePhoto()); + data.put("afterPhoto", report.getAfterPhoto()); + data.put("remark", report.getRemark()); + data.put("userId", report.getUserId()); + data.put("storeId", report.getStoreId()); + data.put("reportToken", report.getReportToken()); + data.put("petName", report.getPetName()); + data.put("serviceType", report.getServiceType()); + data.put("appointmentTime", report.getAppointmentTime()); + data.put("staffName", report.getStaffName()); + data.put("createTime", report.getCreateTime()); + if (store != null) { + Map storeInfo = new HashMap<>(); + storeInfo.put("name", store.getName()); + storeInfo.put("logo", store.getLogo()); + storeInfo.put("phone", store.getPhone()); + storeInfo.put("address", store.getAddress()); + data.put("store", storeInfo); + } + result.put("code", 200); + result.put("data", data); + } else { + result.put("code", 404); + result.put("message", "报告不存在"); + } + return result; + } +} diff --git a/src/main/java/com/petstore/controller/ServiceTypeController.java b/src/main/java/com/petstore/controller/ServiceTypeController.java new file mode 100644 index 0000000..2733a62 --- /dev/null +++ b/src/main/java/com/petstore/controller/ServiceTypeController.java @@ -0,0 +1,52 @@ +package com.petstore.controller; + +import com.petstore.entity.ServiceType; +import com.petstore.service.ServiceTypeService; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@RestController +@RequestMapping("/api/service-type") +@RequiredArgsConstructor +@CrossOrigin +public class ServiceTypeController { + private final ServiceTypeService serviceTypeService; + + @GetMapping("/list") + public Map list(@RequestParam Long storeId) { + List list = serviceTypeService.getByStoreId(storeId); + return Map.of("code", 200, "data", list); + } + + @PostMapping("/create") + public Map create(@RequestBody Map params) { + Long storeId = Long.valueOf(params.get("storeId").toString()); + String name = params.get("name").toString(); + ServiceType created = serviceTypeService.create(storeId, name); + return Map.of("code", 200, "data", created); + } + + @PutMapping("/update") + public Map update(@RequestBody Map params) { + Long id = Long.valueOf(params.get("id").toString()); + String name = params.get("name").toString(); + ServiceType updated = serviceTypeService.update(id, name); + return Map.of("code", 200, "data", updated); + } + + @DeleteMapping("/delete") + public Map delete(@RequestParam Long id) { + serviceTypeService.delete(id); + return Map.of("code", 200, "message", "删除成功"); + } + + @PostMapping("/init") + public Map init() { + serviceTypeService.initDefaults(); + return Map.of("code", 200, "message", "初始化成功"); + } +} diff --git a/src/main/java/com/petstore/controller/SmsController.java b/src/main/java/com/petstore/controller/SmsController.java new file mode 100644 index 0000000..3bb2375 --- /dev/null +++ b/src/main/java/com/petstore/controller/SmsController.java @@ -0,0 +1,45 @@ +package com.petstore.controller; + +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import java.util.HashMap; +import java.util.Map; +import java.util.Random; + +@RestController +@RequestMapping("/api/sms") +@RequiredArgsConstructor +@CrossOrigin +public class SmsController { + + // TODO: 接入真实的短信服务商(阿里云/腾讯云/华为云等) + // 这里用演示模式,随机生成6位验证码 + + @PostMapping("/send") + public Map send(@RequestBody Map params) { + String phone = params.get("phone"); + + Map result = new HashMap<>(); + + if (phone == null || phone.length() != 11) { + result.put("code", 400); + result.put("message", "手机号格式不正确"); + return result; + } + + // 演示:生成6位验证码 + String code = String.format("%06d", new Random().nextInt(999999)); + + // TODO: 调用真实短信服务商发送验证码 + // 阿里云示例: dysmsapi.aliyuncs.com + // 腾讯云示例: sms.tencentcloudapi.com + + System.out.println("【宠伴生活馆】验证码:" + code + ",您正在登录,5分钟内有效。"); + + result.put("code", 200); + result.put("message", "发送成功"); + result.put("data", Map.of("code", code)); // 演示用,实际生产环境不应返回code + return result; + } +} diff --git a/src/main/java/com/petstore/controller/StoreController.java b/src/main/java/com/petstore/controller/StoreController.java new file mode 100644 index 0000000..5048fd9 --- /dev/null +++ b/src/main/java/com/petstore/controller/StoreController.java @@ -0,0 +1,65 @@ +package com.petstore.controller; + +import com.petstore.entity.Store; +import com.petstore.service.StoreService; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import java.util.HashMap; +import java.util.Map; + +@RestController +@RequestMapping("/api/store") +@RequiredArgsConstructor +@CrossOrigin +public class StoreController { + private final StoreService storeService; + + @PostMapping("/register") + public Map register(@RequestBody Store store) { + Store created = storeService.create(store); + Map result = new HashMap<>(); + result.put("code", 200); + result.put("message", "注册成功"); + result.put("data", created); + return result; + } + + @GetMapping("/get") + public Map get(@RequestParam Long id) { + Store store = storeService.findById(id); + Map result = new HashMap<>(); + if (store != null) { + result.put("code", 200); + result.put("data", store); + } else { + result.put("code", 404); + result.put("message", "店铺不存在"); + } + return result; + } + + @PutMapping("/update") + public Map update(@RequestBody Store store) { + Store updated = storeService.update(store); + Map result = new HashMap<>(); + result.put("code", 200); + result.put("message", "更新成功"); + result.put("data", updated); + return result; + } + + @GetMapping("/invite-code") + public Map getByInviteCode(@RequestParam String code) { + Store store = storeService.findByInviteCode(code); + Map result = new HashMap<>(); + if (store != null) { + result.put("code", 200); + result.put("data", store); + } else { + result.put("code", 404); + result.put("message", "邀请码无效"); + } + return result; + } +} diff --git a/src/main/java/com/petstore/controller/UserController.java b/src/main/java/com/petstore/controller/UserController.java new file mode 100644 index 0000000..271e0da --- /dev/null +++ b/src/main/java/com/petstore/controller/UserController.java @@ -0,0 +1,85 @@ +package com.petstore.controller; + +import com.petstore.entity.User; +import com.petstore.service.UserService; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@RestController +@RequestMapping("/api/user") +@RequiredArgsConstructor +@CrossOrigin +public class UserController { + private final UserService userService; + + /** 老板注册店铺 */ + @PostMapping("/register-boss") + public Map registerBoss(@RequestBody Map params) { + String storeName = params.get("storeName"); + String bossName = params.get("bossName"); + String phone = params.get("phone"); + String password = params.get("password"); + return userService.registerBoss(storeName, bossName, phone, password); + } + + /** 登录(老板/员工) */ + @PostMapping("/login") + public Map login(@RequestBody Map params) { + String phone = params.get("phone"); + String code = params.get("code"); + return userService.login(phone, code); + } + + /** 员工注册(邀请码方式) */ + @PostMapping("/register-staff") + public Map registerStaff(@RequestBody Map params) { + String phone = params.get("phone"); + String password = params.get("password"); + String name = params.get("name"); + String inviteCode = params.get("inviteCode"); + return userService.registerStaff(phone, password, name, inviteCode); + } + + /** 老板:创建员工 */ + @PostMapping("/create-staff") + public Map createStaff(@RequestBody Map params) { + Long storeId = Long.valueOf(params.get("storeId").toString()); + String name = params.get("name").toString(); + String phone = params.get("phone").toString(); + return userService.createStaff(storeId, name, phone); + } + + /** 老板:员工列表 */ + @GetMapping("/staff-list") + public Map staffList(@RequestParam Long storeId) { + List list = userService.getStaffList(storeId); + // 不返回密码 + list.forEach(u -> u.setPassword(null)); + return Map.of("code", 200, "data", list); + } + + /** 老板:删除员工 */ + @DeleteMapping("/staff") + public Map deleteStaff(@RequestParam Long staffId) { + userService.deleteStaff(staffId); + return Map.of("code", 200, "message", "删除成功"); + } + + /** 获取用户信息 */ + @GetMapping("/info") + public Map info(@RequestParam Long userId) { + User user = userService.findById(userId); + if (user != null) user.setPassword(null); + return Map.of("code", 200, "data", user); + } + + /** 更新用户信息(头像/姓名/手机号) */ + @PutMapping("/update") + public Map updateUser(@RequestBody Map params) { + return userService.updateUser(params); + } +} diff --git a/src/main/java/com/petstore/controller/WechatController.java b/src/main/java/com/petstore/controller/WechatController.java new file mode 100644 index 0000000..d492447 --- /dev/null +++ b/src/main/java/com/petstore/controller/WechatController.java @@ -0,0 +1,49 @@ +package com.petstore.controller; + +import com.petstore.config.WechatConfig; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import java.util.HashMap; +import java.util.Map; + +@RestController +@RequestMapping("/api/wechat") +@RequiredArgsConstructor +@CrossOrigin +public class WechatController { + private final WechatConfig wechatConfig; + + /** + * 获取微信授权跳转地址 + */ + @GetMapping("/authorize") + public Map getAuthorizeUrl() { + Map result = new HashMap<>(); + result.put("code", 200); + result.put("data", wechatConfig.getAuthorizeUrl()); + return result; + } + + /** + * 微信授权回调 + * 通过 code 换取 access_token,返回用户信息 + */ + @GetMapping("/callback") + public Map callback(@RequestParam String code, @RequestParam String state) { + Map result = new HashMap<>(); + + // TODO: 通过 code 调用微信接口换取 openid 和 access_token + // https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code + + // 演示用:直接返回成功 + result.put("code", 200); + result.put("message", "微信授权成功"); + result.put("data", Map.of( + "openid", "demo_openid_" + code, + "nickname", "微信用户", + "avatar", "" + )); + return result; + } +} diff --git a/src/main/java/com/petstore/entity/Appointment.java b/src/main/java/com/petstore/entity/Appointment.java new file mode 100644 index 0000000..d0f4b2e --- /dev/null +++ b/src/main/java/com/petstore/entity/Appointment.java @@ -0,0 +1,40 @@ +package com.petstore.entity; + +import jakarta.persistence.*; +import lombok.Data; +import java.time.LocalDateTime; + +@Data +@Entity +@Table(name = "t_appointment") +public class Appointment { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private String petName; + private String petType; + private String serviceType; + private LocalDateTime appointmentTime; + + /** 状态: new/doing/done/cancel */ + private String status; + + @Column(name = "store_id") + private Long storeId; + + @Column(name = "user_id") + private Long userId; + + /** 技师ID,开始服务时赋值 */ + @Column(name = "assigned_user_id") + private Long assignedUserId; + + private String remark; + + @Column(name = "create_time") + private LocalDateTime createTime; + + @Column(name = "update_time") + private LocalDateTime updateTime; +} diff --git a/src/main/java/com/petstore/entity/Report.java b/src/main/java/com/petstore/entity/Report.java new file mode 100644 index 0000000..37b5363 --- /dev/null +++ b/src/main/java/com/petstore/entity/Report.java @@ -0,0 +1,47 @@ +package com.petstore.entity; + +import jakarta.persistence.*; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import java.time.LocalDateTime; + +@Data +@Entity +@Table(name = "t_report") +public class Report { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @JsonProperty("appointmentId") + @Column(name = "appointment_id") + private Long appointmentId; + + @Column(name = "before_photo", columnDefinition = "TEXT") + private String beforePhoto; + + @Column(name = "after_photo", columnDefinition = "TEXT") + private String afterPhoto; + + private String remark; + + @Column(name = "user_id") + private Long userId; + + @Column(name = "store_id") + private Long storeId; + + @Column(name = "report_token") + private String reportToken; + + private String petName; + private String serviceType; + private LocalDateTime appointmentTime; + private String staffName; + + @Column(name = "create_time") + private LocalDateTime createTime; + + @Column(name = "update_time") + private LocalDateTime updateTime; +} diff --git a/src/main/java/com/petstore/entity/ServiceType.java b/src/main/java/com/petstore/entity/ServiceType.java new file mode 100644 index 0000000..81042fa --- /dev/null +++ b/src/main/java/com/petstore/entity/ServiceType.java @@ -0,0 +1,22 @@ +package com.petstore.entity; + +import jakarta.persistence.*; +import lombok.Data; +import java.time.LocalDateTime; + +@Data +@Entity +@Table(name = "t_service_type") +public class ServiceType { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + /** 店铺ID,NULL表示系统默认 */ + private Long storeId; + + private String name; + + @Column(name = "create_time") + private LocalDateTime createTime; +} diff --git a/src/main/java/com/petstore/entity/Store.java b/src/main/java/com/petstore/entity/Store.java new file mode 100644 index 0000000..bbfabd6 --- /dev/null +++ b/src/main/java/com/petstore/entity/Store.java @@ -0,0 +1,36 @@ +package com.petstore.entity; + +import jakarta.persistence.*; +import lombok.Data; +import java.time.LocalDateTime; + +@Data +@Entity +@Table(name = "t_store") +public class Store { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private String name; + private String logo; + private String phone; + private String address; + /** 地图选点纬度(WGS84 / 各端与地图接口一致) */ + private Double latitude; + /** 地图选点经度 */ + private Double longitude; + private String intro; + + @Column(name = "owner_id") + private Long ownerId; + + @Column(name = "invite_code") + private String inviteCode; + + @Column(name = "create_time") + private LocalDateTime createTime; + + @Column(name = "update_time") + private LocalDateTime updateTime; +} diff --git a/src/main/java/com/petstore/entity/User.java b/src/main/java/com/petstore/entity/User.java new file mode 100644 index 0000000..e9d4883 --- /dev/null +++ b/src/main/java/com/petstore/entity/User.java @@ -0,0 +1,32 @@ +package com.petstore.entity; + +import jakarta.persistence.*; +import lombok.Data; +import java.time.LocalDateTime; + +@Data +@Entity +@Table(name = "t_user") +public class User { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private String username; + private String password; + private String name; + private String phone; + private String avatar; + + @Column(name = "store_id") + private Long storeId; + + /** 角色:boss / staff / customer */ + private String role; + + @Column(name = "create_time") + private LocalDateTime createTime; + + @Column(name = "update_time") + private LocalDateTime updateTime; +} diff --git a/src/main/java/com/petstore/mapper/AppointmentMapper.java b/src/main/java/com/petstore/mapper/AppointmentMapper.java new file mode 100644 index 0000000..ce524a7 --- /dev/null +++ b/src/main/java/com/petstore/mapper/AppointmentMapper.java @@ -0,0 +1,13 @@ +package com.petstore.mapper; + +import com.petstore.entity.Appointment; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface AppointmentMapper extends JpaRepository { + List findByUserId(Long userId); + List findByUserIdAndStatus(Long userId, String status); + List findByStoreId(Long storeId); + List findByStoreIdAndStatus(Long storeId, String status); +} diff --git a/src/main/java/com/petstore/mapper/ReportMapper.java b/src/main/java/com/petstore/mapper/ReportMapper.java new file mode 100644 index 0000000..5a217d9 --- /dev/null +++ b/src/main/java/com/petstore/mapper/ReportMapper.java @@ -0,0 +1,7 @@ +package com.petstore.mapper; + +import com.petstore.entity.Report; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ReportMapper extends JpaRepository { +} diff --git a/src/main/java/com/petstore/mapper/ServiceTypeMapper.java b/src/main/java/com/petstore/mapper/ServiceTypeMapper.java new file mode 100644 index 0000000..045d735 --- /dev/null +++ b/src/main/java/com/petstore/mapper/ServiceTypeMapper.java @@ -0,0 +1,11 @@ +package com.petstore.mapper; + +import com.petstore.entity.ServiceType; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface ServiceTypeMapper extends JpaRepository { + List findByStoreIdOrStoreIdIsNull(Long storeId); + List findByStoreId(Long storeId); +} diff --git a/src/main/java/com/petstore/mapper/StoreMapper.java b/src/main/java/com/petstore/mapper/StoreMapper.java new file mode 100644 index 0000000..94bfa8e --- /dev/null +++ b/src/main/java/com/petstore/mapper/StoreMapper.java @@ -0,0 +1,8 @@ +package com.petstore.mapper; + +import com.petstore.entity.Store; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface StoreMapper extends JpaRepository { + Store findByInviteCode(String inviteCode); +} diff --git a/src/main/java/com/petstore/mapper/UserMapper.java b/src/main/java/com/petstore/mapper/UserMapper.java new file mode 100644 index 0000000..506e267 --- /dev/null +++ b/src/main/java/com/petstore/mapper/UserMapper.java @@ -0,0 +1,12 @@ +package com.petstore.mapper; + +import com.petstore.entity.User; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface UserMapper extends JpaRepository { + User findByUsername(String username); + User findByPhone(String phone); + List findByStoreId(Long storeId); +} diff --git a/src/main/java/com/petstore/service/AppointmentService.java b/src/main/java/com/petstore/service/AppointmentService.java new file mode 100644 index 0000000..5ac4dea --- /dev/null +++ b/src/main/java/com/petstore/service/AppointmentService.java @@ -0,0 +1,63 @@ +package com.petstore.service; + +import com.petstore.entity.Appointment; +import com.petstore.mapper.AppointmentMapper; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.List; + +@Service +@RequiredArgsConstructor +public class AppointmentService { + private final AppointmentMapper appointmentMapper; + + // 员工查看自己的预约 + public List getByUserId(Long userId) { + return appointmentMapper.findByUserId(userId); + } + + // 老板查看本店所有预约 + public List getByStoreId(Long storeId) { + return appointmentMapper.findByStoreId(storeId); + } + + // 员工按状态查 + public List getByUserIdAndStatus(Long userId, String status) { + return appointmentMapper.findByUserIdAndStatus(userId, status); + } + + // 老板按状态查 + public List getByStoreIdAndStatus(Long storeId, String status) { + return appointmentMapper.findByStoreIdAndStatus(storeId, status); + } + + public Appointment create(Appointment appointment) { + appointment.setCreateTime(LocalDateTime.now()); + appointment.setUpdateTime(LocalDateTime.now()); + return appointmentMapper.save(appointment); + } + + public Appointment updateStatus(Long id, String status) { + Appointment appointment = appointmentMapper.findById(id).orElse(null); + if (appointment != null) { + appointment.setStatus(status); + appointment.setUpdateTime(LocalDateTime.now()); + return appointmentMapper.save(appointment); + } + return null; + } + + /** 开始服务:状态变为进行中,同时指定技师为当前用户 */ + public Appointment startService(Long appointmentId, Long staffUserId) { + Appointment appointment = appointmentMapper.findById(appointmentId).orElse(null); + if (appointment != null) { + appointment.setStatus("doing"); + appointment.setAssignedUserId(staffUserId); + appointment.setUpdateTime(LocalDateTime.now()); + return appointmentMapper.save(appointment); + } + return null; + } +} diff --git a/src/main/java/com/petstore/service/ReportService.java b/src/main/java/com/petstore/service/ReportService.java new file mode 100644 index 0000000..b020714 --- /dev/null +++ b/src/main/java/com/petstore/service/ReportService.java @@ -0,0 +1,86 @@ +package com.petstore.service; + +import com.petstore.entity.Appointment; +import com.petstore.entity.Report; +import com.petstore.entity.User; +import com.petstore.mapper.AppointmentMapper; +import com.petstore.mapper.ReportMapper; +import com.petstore.mapper.UserMapper; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class ReportService { + private final ReportMapper reportMapper; + private final AppointmentMapper appointmentMapper; + private final UserMapper userMapper; + + public Report create(Report report) { + // 生成唯一令牌 + report.setReportToken(UUID.randomUUID().toString().replace("-", "")); + + // 填充冗余字段,并自动完成预约 + if (report.getAppointmentId() != null) { + Appointment appt = appointmentMapper.findById(report.getAppointmentId()).orElse(null); + if (appt != null) { + report.setPetName(appt.getPetName()); + report.setServiceType(appt.getServiceType()); + report.setAppointmentTime(appt.getAppointmentTime()); + report.setStoreId(appt.getStoreId()); + // 技师取预约分配的技师(开始服务时指定的) + if (appt.getAssignedUserId() != null) { + User staff = userMapper.findById(appt.getAssignedUserId()).orElse(null); + if (staff != null) { + report.setUserId(staff.getId()); + report.setStaffName(staff.getName()); + } + } + // 填写完报告,自动标记预约为已完成 + appt.setStatus("done"); + appt.setUpdateTime(LocalDateTime.now()); + appointmentMapper.save(appt); + } + } + // 如果预约没分配技师,则用当前操作人 + if (report.getUserId() != null && report.getStaffName() == null) { + User staff = userMapper.findById(report.getUserId()).orElse(null); + if (staff != null) { + report.setStaffName(staff.getName()); + if (report.getStoreId() == null) { + report.setStoreId(staff.getStoreId()); + } + } + } + + report.setCreateTime(LocalDateTime.now()); + report.setUpdateTime(LocalDateTime.now()); + return reportMapper.save(report); + } + + public Report getByAppointmentId(Long appointmentId) { + return reportMapper.findAll().stream() + .filter(r -> r.getAppointmentId().equals(appointmentId)) + .findFirst() + .orElse(null); + } + + public Report getByToken(String token) { + return reportMapper.findAll().stream() + .filter(r -> token.equals(r.getReportToken())) + .findFirst() + .orElse(null); + } + + public List list(Long storeId, Long userId) { + return reportMapper.findAll().stream() + .filter(r -> storeId == null || storeId.equals(r.getStoreId())) + .filter(r -> userId == null || userId.equals(r.getUserId())) + .collect(Collectors.toList()); + } +} diff --git a/src/main/java/com/petstore/service/ServiceTypeService.java b/src/main/java/com/petstore/service/ServiceTypeService.java new file mode 100644 index 0000000..9953d32 --- /dev/null +++ b/src/main/java/com/petstore/service/ServiceTypeService.java @@ -0,0 +1,59 @@ +package com.petstore.service; + +import com.petstore.entity.ServiceType; +import com.petstore.mapper.ServiceTypeMapper; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.List; + +@Service +@RequiredArgsConstructor +public class ServiceTypeService { + private final ServiceTypeMapper serviceTypeMapper; + + /** 获取服务类型(系统默认 + 当前店铺自定义) */ + public List getByStoreId(Long storeId) { + return serviceTypeMapper.findByStoreIdOrStoreIdIsNull(storeId); + } + + /** 老板新增服务类型 */ + public ServiceType create(Long storeId, String name) { + ServiceType st = new ServiceType(); + st.setStoreId(storeId); + st.setName(name); + st.setCreateTime(LocalDateTime.now()); + return serviceTypeMapper.save(st); + } + + /** 老板编辑服务类型 */ + public ServiceType update(Long id, String name) { + ServiceType st = serviceTypeMapper.findById(id).orElse(null); + if (st != null) { + st.setName(name); + serviceTypeMapper.save(st); + } + return st; + } + + /** 老板删除服务类型(仅能删除自己店铺的) */ + public void delete(Long id) { + serviceTypeMapper.deleteById(id); + } + + /** 初始化系统默认服务类型(如果不存在) */ + public void initDefaults() { + List defaults = serviceTypeMapper.findByStoreIdOrStoreIdIsNull(null); + if (defaults.isEmpty()) { + String[] names = {"洗澡", "美容", "洗澡+美容", "剪指甲", "驱虫"}; + for (String name : names) { + ServiceType st = new ServiceType(); + st.setStoreId(null); + st.setName(name); + st.setCreateTime(LocalDateTime.now()); + serviceTypeMapper.save(st); + } + } + } +} diff --git a/src/main/java/com/petstore/service/StoreService.java b/src/main/java/com/petstore/service/StoreService.java new file mode 100644 index 0000000..e6f31d7 --- /dev/null +++ b/src/main/java/com/petstore/service/StoreService.java @@ -0,0 +1,39 @@ +package com.petstore.service; + +import com.petstore.entity.Store; +import com.petstore.mapper.StoreMapper; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.UUID; + +@Service +@RequiredArgsConstructor +public class StoreService { + private final StoreMapper storeMapper; + + public Store create(Store store) { + store.setInviteCode(generateInviteCode()); + store.setCreateTime(LocalDateTime.now()); + store.setUpdateTime(LocalDateTime.now()); + return storeMapper.save(store); + } + + public Store findById(Long id) { + return storeMapper.findById(id).orElse(null); + } + + public Store findByInviteCode(String code) { + return storeMapper.findByInviteCode(code); + } + + public Store update(Store store) { + store.setUpdateTime(LocalDateTime.now()); + return storeMapper.save(store); + } + + private String generateInviteCode() { + return UUID.randomUUID().toString().replace("-", "").substring(0, 8).toUpperCase(); + } +} diff --git a/src/main/java/com/petstore/service/UserService.java b/src/main/java/com/petstore/service/UserService.java new file mode 100644 index 0000000..538957c --- /dev/null +++ b/src/main/java/com/petstore/service/UserService.java @@ -0,0 +1,168 @@ +package com.petstore.service; + +import com.petstore.entity.Store; +import com.petstore.entity.User; +import com.petstore.mapper.StoreMapper; +import com.petstore.mapper.UserMapper; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.*; + +@Service +@RequiredArgsConstructor +public class UserService { + private final UserMapper userMapper; + private final StoreMapper storeMapper; + + public Map registerBoss(String storeName, String bossName, String phone, String password) { + if (userMapper.findByPhone(phone) != null) { + return Map.of("code", 400, "message", "手机号已注册"); + } + + Store store = new Store(); + store.setName(storeName); + store.setPhone(phone); + store.setOwnerId(0L); + store.setInviteCode(UUID.randomUUID().toString().replace("-", "").substring(0, 8).toUpperCase()); + store.setCreateTime(LocalDateTime.now()); + store.setUpdateTime(LocalDateTime.now()); + store = storeMapper.save(store); + + User boss = new User(); + boss.setUsername(phone); + boss.setName(bossName); + boss.setPhone(phone); + boss.setPassword(password); + boss.setStoreId(store.getId()); + boss.setRole("boss"); + boss.setCreateTime(LocalDateTime.now()); + boss.setUpdateTime(LocalDateTime.now()); + boss = userMapper.save(boss); + + store.setOwnerId(boss.getId()); + storeMapper.save(store); + + Map data = new HashMap<>(); + data.put("user", boss); + data.put("store", store); + return Map.of("code", 200, "message", "注册成功", "data", data); + } + + public Map login(String phone, String code) { + // 演示模式:验证码 123456 万能 + if (!"123456".equals(code)) { + return Map.of("code", 401, "message", "验证码错误"); + } + User user = userMapper.findByPhone(phone); + if (user == null) { + // 自动注册为 C 端用户 (customer) + user = new User(); + user.setUsername(phone); + user.setPhone(phone); + user.setName("微信用户" + phone.substring(7)); + user.setRole("customer"); + user.setCreateTime(LocalDateTime.now()); + user.setUpdateTime(LocalDateTime.now()); + user = userMapper.save(user); + } + Store store = null; + if (user.getStoreId() != null) { + store = storeMapper.findById(user.getStoreId()).orElse(null); + } + Map data = new HashMap<>(); + data.put("user", user); + data.put("store", store); + return Map.of("code", 200, "message", "登录成功", "data", data); + } + + public Map registerStaff(String phone, String password, String name, String inviteCode) { + if (userMapper.findByPhone(phone) != null) { + return Map.of("code", 400, "message", "手机号已注册"); + } + Store store = storeMapper.findByInviteCode(inviteCode); + if (store == null) { + return Map.of("code", 400, "message", "邀请码无效"); + } + User staff = new User(); + staff.setUsername(phone); + staff.setPhone(phone); + staff.setPassword(password); + staff.setName(name); + staff.setStoreId(store.getId()); + staff.setRole("staff"); + staff.setCreateTime(LocalDateTime.now()); + staff.setUpdateTime(LocalDateTime.now()); + staff = userMapper.save(staff); + Map data = new HashMap<>(); + data.put("user", staff); + data.put("store", store); + return Map.of("code", 200, "message", "注册成功", "data", data); + } + + public Map createStaff(Long storeId, String name, String phone) { + if (storeId == null) { + return Map.of("code", 400, "message", "店铺ID不能为空"); + } + if (userMapper.findByPhone(phone) != null) { + return Map.of("code", 400, "message", "手机号已存在"); + } + String pwd = String.format("%06d", (int)(Math.random() * 999999)); + User staff = new User(); + staff.setUsername(phone); + staff.setName(name); + staff.setPhone(phone); + staff.setPassword(pwd); + staff.setStoreId(storeId); + staff.setRole("staff"); + staff.setCreateTime(LocalDateTime.now()); + staff.setUpdateTime(LocalDateTime.now()); + staff = userMapper.save(staff); + return Map.of("code", 200, "message", "创建成功,初始密码:" + pwd, "data", staff); + } + + public List getStaffList(Long storeId) { + return userMapper.findByStoreId(storeId); + } + + public void deleteStaff(Long staffId) { + userMapper.deleteById(staffId); + } + + public User findById(Long id) { + return userMapper.findById(id).orElse(null); + } + + public Map updateUser(Map params) { + Long userId = Long.valueOf(params.get("id").toString()); + User user = userMapper.findById(userId).orElse(null); + if (user == null) { + return Map.of("code", 404, "message", "用户不存在"); + } + if (params.containsKey("name") && params.get("name") != null) { + user.setName(params.get("name").toString()); + } + if (params.containsKey("phone") && params.get("phone") != null) { + String newPhone = params.get("phone").toString(); + // 验证码校验 + String code = params.get("code") != null ? params.get("code").toString() : ""; + if (!"123456".equals(code)) { + return Map.of("code", 400, "message", "验证码错误"); + } + // 检查手机号是否被占用 + User existing = userMapper.findByPhone(newPhone); + if (existing != null && !existing.getId().equals(userId)) { + return Map.of("code", 400, "message", "手机号已被占用"); + } + user.setPhone(newPhone); + } + if (params.containsKey("avatar") && params.get("avatar") != null) { + user.setAvatar(params.get("avatar").toString()); + } + user.setUpdateTime(LocalDateTime.now()); + userMapper.save(user); + user.setPassword(null); + return Map.of("code", 200, "message", "更新成功", "data", user); + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..5ef7cee --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,42 @@ +spring: + application: + name: petstore-backend + datasource: + url: jdbc:mysql://192.144.152.238:3306/petstore?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&autoReconnect=true + username: root + password: Wabjtam123@ + driver-class-name: com.mysql.cj.jdbc.Driver + hikari: + max-lifetime: 1800000 # 30分钟,必须小于MySQL的wait_timeout(默认8小时) + connection-test-query: SELECT 1 + validation-timeout: 3000 + idle-timeout: 600000 # 10分钟 + jpa: + hibernate: + ddl-auto: update + show-sql: true + properties: + hibernate: + dialect: org.hibernate.dialect.MySQLDialect + format_sql: true + servlet: + multipart: + max-file-size: 10MB + +server: + port: 8080 + servlet: + context-path: + +upload: + path: uploads + +logging: + level: + com.petstore: debug + +# 微信登录配置(需替换为实际值) +wechat: + appid: YOUR_APPID + appsecret: YOUR_APPSECRET + redirect_uri: http://localhost:8080/api/wechat/callback diff --git a/uploads/2026/04/01/2ee159a706484affb95e785b4742ca3b.jpg b/uploads/2026/04/01/2ee159a706484affb95e785b4742ca3b.jpg new file mode 100644 index 0000000..b87a82a Binary files /dev/null and b/uploads/2026/04/01/2ee159a706484affb95e785b4742ca3b.jpg differ diff --git a/uploads/2026/04/01/313b13375c304aa387aa87475feae2c8.jpg b/uploads/2026/04/01/313b13375c304aa387aa87475feae2c8.jpg new file mode 100644 index 0000000..b87a82a Binary files /dev/null and b/uploads/2026/04/01/313b13375c304aa387aa87475feae2c8.jpg differ diff --git a/uploads/2026/04/01/3a4529e8daac48f7a63acd6dbf71a2f9.jpg b/uploads/2026/04/01/3a4529e8daac48f7a63acd6dbf71a2f9.jpg new file mode 100644 index 0000000..b87a82a Binary files /dev/null and b/uploads/2026/04/01/3a4529e8daac48f7a63acd6dbf71a2f9.jpg differ diff --git a/uploads/2026/04/01/3efde5ed9e2b460d8890e7f77f5444aa.jpg b/uploads/2026/04/01/3efde5ed9e2b460d8890e7f77f5444aa.jpg new file mode 100644 index 0000000..b87a82a Binary files /dev/null and b/uploads/2026/04/01/3efde5ed9e2b460d8890e7f77f5444aa.jpg differ diff --git a/uploads/2026/04/01/6455dbaa05724d28b9240ce1f35212db.jpg b/uploads/2026/04/01/6455dbaa05724d28b9240ce1f35212db.jpg new file mode 100644 index 0000000..b87a82a Binary files /dev/null and b/uploads/2026/04/01/6455dbaa05724d28b9240ce1f35212db.jpg differ diff --git a/uploads/2026/04/01/6e8a6f450b6041d7984cb89092b5c03e.jpg b/uploads/2026/04/01/6e8a6f450b6041d7984cb89092b5c03e.jpg new file mode 100644 index 0000000..b87a82a Binary files /dev/null and b/uploads/2026/04/01/6e8a6f450b6041d7984cb89092b5c03e.jpg differ diff --git a/uploads/2026/04/01/708f86a8cb5d444380659f13011b3844.jpg b/uploads/2026/04/01/708f86a8cb5d444380659f13011b3844.jpg new file mode 100644 index 0000000..b87a82a Binary files /dev/null and b/uploads/2026/04/01/708f86a8cb5d444380659f13011b3844.jpg differ diff --git a/uploads/2026/04/01/71dfce36906c45c7a33b2e7ff76107f1.jpg b/uploads/2026/04/01/71dfce36906c45c7a33b2e7ff76107f1.jpg new file mode 100644 index 0000000..b87a82a Binary files /dev/null and b/uploads/2026/04/01/71dfce36906c45c7a33b2e7ff76107f1.jpg differ diff --git a/uploads/2026/04/01/79e7d0e7caa945fbb253dfcad0704970.jpg b/uploads/2026/04/01/79e7d0e7caa945fbb253dfcad0704970.jpg new file mode 100644 index 0000000..b87a82a Binary files /dev/null and b/uploads/2026/04/01/79e7d0e7caa945fbb253dfcad0704970.jpg differ diff --git a/uploads/2026/04/01/97dca4475fe74e489cc41e59715cce24.jpg b/uploads/2026/04/01/97dca4475fe74e489cc41e59715cce24.jpg new file mode 100644 index 0000000..b87a82a Binary files /dev/null and b/uploads/2026/04/01/97dca4475fe74e489cc41e59715cce24.jpg differ diff --git a/uploads/2026/04/01/a3adb46ccc8d4927be536ab006a6e484.jpg b/uploads/2026/04/01/a3adb46ccc8d4927be536ab006a6e484.jpg new file mode 100644 index 0000000..b87a82a Binary files /dev/null and b/uploads/2026/04/01/a3adb46ccc8d4927be536ab006a6e484.jpg differ diff --git a/uploads/2026/04/01/d48b1611480f419f90f47c6561e4ef85.jpg b/uploads/2026/04/01/d48b1611480f419f90f47c6561e4ef85.jpg new file mode 100644 index 0000000..b87a82a Binary files /dev/null and b/uploads/2026/04/01/d48b1611480f419f90f47c6561e4ef85.jpg differ diff --git a/uploads/2026/04/10/09430d9868874ba3b2893b67101d7ad5.jpg b/uploads/2026/04/10/09430d9868874ba3b2893b67101d7ad5.jpg new file mode 100644 index 0000000..b87a82a Binary files /dev/null and b/uploads/2026/04/10/09430d9868874ba3b2893b67101d7ad5.jpg differ diff --git a/uploads/2026/04/10/73c2f8330fe34dd394d4f347bef1ef6d.jpg b/uploads/2026/04/10/73c2f8330fe34dd394d4f347bef1ef6d.jpg new file mode 100644 index 0000000..b87a82a Binary files /dev/null and b/uploads/2026/04/10/73c2f8330fe34dd394d4f347bef1ef6d.jpg differ diff --git a/uploads/2026/04/10/e85c11305e514239a620292726e49ee2.jpg b/uploads/2026/04/10/e85c11305e514239a620292726e49ee2.jpg new file mode 100644 index 0000000..b87a82a Binary files /dev/null and b/uploads/2026/04/10/e85c11305e514239a620292726e49ee2.jpg differ diff --git a/uploads/2026/04/10/fe98df307d304b36810e8aa617296310.jpg b/uploads/2026/04/10/fe98df307d304b36810e8aa617296310.jpg new file mode 100644 index 0000000..b87a82a Binary files /dev/null and b/uploads/2026/04/10/fe98df307d304b36810e8aa617296310.jpg differ diff --git a/uploads/2026/04/11/91e6b16f3f7f46759ecddfbbb056cda9.jpg b/uploads/2026/04/11/91e6b16f3f7f46759ecddfbbb056cda9.jpg new file mode 100644 index 0000000..b87a82a Binary files /dev/null and b/uploads/2026/04/11/91e6b16f3f7f46759ecddfbbb056cda9.jpg differ diff --git a/uploads/2026/04/11/9e8439362f2e424c900824c309669812.jpg b/uploads/2026/04/11/9e8439362f2e424c900824c309669812.jpg new file mode 100644 index 0000000..b87a82a Binary files /dev/null and b/uploads/2026/04/11/9e8439362f2e424c900824c309669812.jpg differ diff --git a/uploads/2026/04/11/f14879d02f2a45178f28bd72bbb44998.jpg b/uploads/2026/04/11/f14879d02f2a45178f28bd72bbb44998.jpg new file mode 100644 index 0000000..b87a82a Binary files /dev/null and b/uploads/2026/04/11/f14879d02f2a45178f28bd72bbb44998.jpg differ diff --git a/uploads/2026/04/12/2742566684944b6fb52cdc4462ae57b5.jpg b/uploads/2026/04/12/2742566684944b6fb52cdc4462ae57b5.jpg new file mode 100644 index 0000000..b87a82a Binary files /dev/null and b/uploads/2026/04/12/2742566684944b6fb52cdc4462ae57b5.jpg differ diff --git a/uploads/2026/04/12/9a0b112e1a50470faaf301b46c6fbe7d.jpg b/uploads/2026/04/12/9a0b112e1a50470faaf301b46c6fbe7d.jpg new file mode 100644 index 0000000..b87a82a Binary files /dev/null and b/uploads/2026/04/12/9a0b112e1a50470faaf301b46c6fbe7d.jpg differ diff --git a/uploads/2026/04/12/9c5b51c768fd49fd9bb8fc9e10bd62ac.jpg b/uploads/2026/04/12/9c5b51c768fd49fd9bb8fc9e10bd62ac.jpg new file mode 100644 index 0000000..b87a82a Binary files /dev/null and b/uploads/2026/04/12/9c5b51c768fd49fd9bb8fc9e10bd62ac.jpg differ diff --git a/uploads/2026/04/12/a0ae7927c9544db5b293edfd6e2ddd04.jpg b/uploads/2026/04/12/a0ae7927c9544db5b293edfd6e2ddd04.jpg new file mode 100644 index 0000000..b87a82a Binary files /dev/null and b/uploads/2026/04/12/a0ae7927c9544db5b293edfd6e2ddd04.jpg differ diff --git a/uploads/2026/04/12/a84696ea7b504e9ab25ed2e9b491bf16.jpg b/uploads/2026/04/12/a84696ea7b504e9ab25ed2e9b491bf16.jpg new file mode 100644 index 0000000..b87a82a Binary files /dev/null and b/uploads/2026/04/12/a84696ea7b504e9ab25ed2e9b491bf16.jpg differ diff --git a/uploads/2026/04/12/c125b6c72ba4474696815ee4cc1058d3.jpg b/uploads/2026/04/12/c125b6c72ba4474696815ee4cc1058d3.jpg new file mode 100644 index 0000000..b87a82a Binary files /dev/null and b/uploads/2026/04/12/c125b6c72ba4474696815ee4cc1058d3.jpg differ diff --git a/uploads/2026/04/12/c6f00e5f90844e669da25df62fcfde4b.jpg b/uploads/2026/04/12/c6f00e5f90844e669da25df62fcfde4b.jpg new file mode 100644 index 0000000..b87a82a Binary files /dev/null and b/uploads/2026/04/12/c6f00e5f90844e669da25df62fcfde4b.jpg differ diff --git a/uploads/2026/04/12/e3484bf402354c3389c50a93230fa886.jpg b/uploads/2026/04/12/e3484bf402354c3389c50a93230fa886.jpg new file mode 100644 index 0000000..b87a82a Binary files /dev/null and b/uploads/2026/04/12/e3484bf402354c3389c50a93230fa886.jpg differ