Initial commit
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
target/
|
||||
96
pom.xml
Normal file
@ -0,0 +1,96 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>3.2.3</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
|
||||
<groupId>com.petstore</groupId>
|
||||
<artifactId>petstore-backend</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<packaging>jar</packaging>
|
||||
<name>petstore-backend</name>
|
||||
<description>宠伴生活馆 后端服务</description>
|
||||
|
||||
<properties>
|
||||
<java.version>17</java.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.mysql</groupId>
|
||||
<artifactId>mysql-connector-j</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.18.44</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.13.0</version>
|
||||
<configuration>
|
||||
<source>17</source>
|
||||
<target>17</target>
|
||||
<fork>true</fork>
|
||||
<compilerArgs>
|
||||
<arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED</arg>
|
||||
<arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED</arg>
|
||||
<arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED</arg>
|
||||
<arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED</arg>
|
||||
<arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED</arg>
|
||||
<arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED</arg>
|
||||
<arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED</arg>
|
||||
<arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED</arg>
|
||||
<arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED</arg>
|
||||
<arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.jvm=ALL-UNNAMED</arg>
|
||||
</compilerArgs>
|
||||
<annotationProcessorPaths>
|
||||
<path>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.18.44</version>
|
||||
</path>
|
||||
</annotationProcessorPaths>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<excludes>
|
||||
<exclude>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
</exclude>
|
||||
</excludes>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
30
src/main/java/com/petstore/PetstoreApplication.java
Normal file
@ -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/%'");
|
||||
};
|
||||
}
|
||||
}
|
||||
23
src/main/java/com/petstore/config/CorsConfig.java
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
27
src/main/java/com/petstore/config/WebConfig.java
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
33
src/main/java/com/petstore/config/WechatConfig.java
Normal file
@ -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";
|
||||
}
|
||||
}
|
||||
@ -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<String, Object> list(
|
||||
@RequestParam(required = false) Long userId,
|
||||
@RequestParam(required = false) Long storeId,
|
||||
@RequestParam(required = false) String status) {
|
||||
|
||||
List<Appointment> 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<String, Object> create(@RequestBody Map<String, Object> 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<String, Object> start(@RequestBody Map<String, Object> 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<String, Object> 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", "预约不存在");
|
||||
}
|
||||
}
|
||||
114
src/main/java/com/petstore/controller/FileController.java
Normal file
@ -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<Resource> 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<Resource> 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<String, Object> uploadImage(@RequestParam("file") MultipartFile file) {
|
||||
Map<String, Object> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
117
src/main/java/com/petstore/controller/ReportController.java
Normal file
@ -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<String, Object> 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<String, Object> 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<String, Object> list(@RequestParam(required = false) Long storeId,
|
||||
@RequestParam(required = false) Long userId) {
|
||||
List<Report> reports = reportService.list(storeId, userId);
|
||||
// 附加技师名称,并补全图片URL
|
||||
List<Map<String, Object>> data = reports.stream().map(r -> {
|
||||
Map<String, Object> 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<String, Object> 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<String, Object> result = new HashMap<>();
|
||||
if (report != null) {
|
||||
// 附加店铺信息
|
||||
Store store = null;
|
||||
if (report.getStoreId() != null) {
|
||||
store = storeService.findById(report.getStoreId());
|
||||
}
|
||||
Map<String, Object> 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<String, Object> 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;
|
||||
}
|
||||
}
|
||||
@ -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<String, Object> list(@RequestParam Long storeId) {
|
||||
List<ServiceType> list = serviceTypeService.getByStoreId(storeId);
|
||||
return Map.of("code", 200, "data", list);
|
||||
}
|
||||
|
||||
@PostMapping("/create")
|
||||
public Map<String, Object> create(@RequestBody Map<String, Object> 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<String, Object> update(@RequestBody Map<String, Object> 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<String, Object> delete(@RequestParam Long id) {
|
||||
serviceTypeService.delete(id);
|
||||
return Map.of("code", 200, "message", "删除成功");
|
||||
}
|
||||
|
||||
@PostMapping("/init")
|
||||
public Map<String, Object> init() {
|
||||
serviceTypeService.initDefaults();
|
||||
return Map.of("code", 200, "message", "初始化成功");
|
||||
}
|
||||
}
|
||||
45
src/main/java/com/petstore/controller/SmsController.java
Normal file
@ -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<String, Object> send(@RequestBody Map<String, String> params) {
|
||||
String phone = params.get("phone");
|
||||
|
||||
Map<String, Object> 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;
|
||||
}
|
||||
}
|
||||
65
src/main/java/com/petstore/controller/StoreController.java
Normal file
@ -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<String, Object> register(@RequestBody Store store) {
|
||||
Store created = storeService.create(store);
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("code", 200);
|
||||
result.put("message", "注册成功");
|
||||
result.put("data", created);
|
||||
return result;
|
||||
}
|
||||
|
||||
@GetMapping("/get")
|
||||
public Map<String, Object> get(@RequestParam Long id) {
|
||||
Store store = storeService.findById(id);
|
||||
Map<String, Object> 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<String, Object> update(@RequestBody Store store) {
|
||||
Store updated = storeService.update(store);
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("code", 200);
|
||||
result.put("message", "更新成功");
|
||||
result.put("data", updated);
|
||||
return result;
|
||||
}
|
||||
|
||||
@GetMapping("/invite-code")
|
||||
public Map<String, Object> getByInviteCode(@RequestParam String code) {
|
||||
Store store = storeService.findByInviteCode(code);
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
if (store != null) {
|
||||
result.put("code", 200);
|
||||
result.put("data", store);
|
||||
} else {
|
||||
result.put("code", 404);
|
||||
result.put("message", "邀请码无效");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
85
src/main/java/com/petstore/controller/UserController.java
Normal file
@ -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<String, Object> registerBoss(@RequestBody Map<String, String> 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<String, Object> login(@RequestBody Map<String, String> params) {
|
||||
String phone = params.get("phone");
|
||||
String code = params.get("code");
|
||||
return userService.login(phone, code);
|
||||
}
|
||||
|
||||
/** 员工注册(邀请码方式) */
|
||||
@PostMapping("/register-staff")
|
||||
public Map<String, Object> registerStaff(@RequestBody Map<String, String> 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<String, Object> createStaff(@RequestBody Map<String, Object> 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<String, Object> staffList(@RequestParam Long storeId) {
|
||||
List<User> list = userService.getStaffList(storeId);
|
||||
// 不返回密码
|
||||
list.forEach(u -> u.setPassword(null));
|
||||
return Map.of("code", 200, "data", list);
|
||||
}
|
||||
|
||||
/** 老板:删除员工 */
|
||||
@DeleteMapping("/staff")
|
||||
public Map<String, Object> deleteStaff(@RequestParam Long staffId) {
|
||||
userService.deleteStaff(staffId);
|
||||
return Map.of("code", 200, "message", "删除成功");
|
||||
}
|
||||
|
||||
/** 获取用户信息 */
|
||||
@GetMapping("/info")
|
||||
public Map<String, Object> 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<String, Object> updateUser(@RequestBody Map<String, Object> params) {
|
||||
return userService.updateUser(params);
|
||||
}
|
||||
}
|
||||
49
src/main/java/com/petstore/controller/WechatController.java
Normal file
@ -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<String, Object> getAuthorizeUrl() {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("code", 200);
|
||||
result.put("data", wechatConfig.getAuthorizeUrl());
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 微信授权回调
|
||||
* 通过 code 换取 access_token,返回用户信息
|
||||
*/
|
||||
@GetMapping("/callback")
|
||||
public Map<String, Object> callback(@RequestParam String code, @RequestParam String state) {
|
||||
Map<String, Object> 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;
|
||||
}
|
||||
}
|
||||
40
src/main/java/com/petstore/entity/Appointment.java
Normal file
@ -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;
|
||||
}
|
||||
47
src/main/java/com/petstore/entity/Report.java
Normal file
@ -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;
|
||||
}
|
||||
22
src/main/java/com/petstore/entity/ServiceType.java
Normal file
@ -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;
|
||||
}
|
||||
36
src/main/java/com/petstore/entity/Store.java
Normal file
@ -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;
|
||||
}
|
||||
32
src/main/java/com/petstore/entity/User.java
Normal file
@ -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;
|
||||
}
|
||||
13
src/main/java/com/petstore/mapper/AppointmentMapper.java
Normal file
@ -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<Appointment, Long> {
|
||||
List<Appointment> findByUserId(Long userId);
|
||||
List<Appointment> findByUserIdAndStatus(Long userId, String status);
|
||||
List<Appointment> findByStoreId(Long storeId);
|
||||
List<Appointment> findByStoreIdAndStatus(Long storeId, String status);
|
||||
}
|
||||
7
src/main/java/com/petstore/mapper/ReportMapper.java
Normal file
@ -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<Report, Long> {
|
||||
}
|
||||
11
src/main/java/com/petstore/mapper/ServiceTypeMapper.java
Normal file
@ -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<ServiceType, Long> {
|
||||
List<ServiceType> findByStoreIdOrStoreIdIsNull(Long storeId);
|
||||
List<ServiceType> findByStoreId(Long storeId);
|
||||
}
|
||||
8
src/main/java/com/petstore/mapper/StoreMapper.java
Normal file
@ -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, Long> {
|
||||
Store findByInviteCode(String inviteCode);
|
||||
}
|
||||
12
src/main/java/com/petstore/mapper/UserMapper.java
Normal file
@ -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, Long> {
|
||||
User findByUsername(String username);
|
||||
User findByPhone(String phone);
|
||||
List<User> findByStoreId(Long storeId);
|
||||
}
|
||||
63
src/main/java/com/petstore/service/AppointmentService.java
Normal file
@ -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<Appointment> getByUserId(Long userId) {
|
||||
return appointmentMapper.findByUserId(userId);
|
||||
}
|
||||
|
||||
// 老板查看本店所有预约
|
||||
public List<Appointment> getByStoreId(Long storeId) {
|
||||
return appointmentMapper.findByStoreId(storeId);
|
||||
}
|
||||
|
||||
// 员工按状态查
|
||||
public List<Appointment> getByUserIdAndStatus(Long userId, String status) {
|
||||
return appointmentMapper.findByUserIdAndStatus(userId, status);
|
||||
}
|
||||
|
||||
// 老板按状态查
|
||||
public List<Appointment> 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;
|
||||
}
|
||||
}
|
||||
86
src/main/java/com/petstore/service/ReportService.java
Normal file
@ -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<Report> 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());
|
||||
}
|
||||
}
|
||||
59
src/main/java/com/petstore/service/ServiceTypeService.java
Normal file
@ -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<ServiceType> 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<ServiceType> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
39
src/main/java/com/petstore/service/StoreService.java
Normal file
@ -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();
|
||||
}
|
||||
}
|
||||
168
src/main/java/com/petstore/service/UserService.java
Normal file
@ -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<String, Object> 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<String, Object> data = new HashMap<>();
|
||||
data.put("user", boss);
|
||||
data.put("store", store);
|
||||
return Map.of("code", 200, "message", "注册成功", "data", data);
|
||||
}
|
||||
|
||||
public Map<String, Object> 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<String, Object> data = new HashMap<>();
|
||||
data.put("user", user);
|
||||
data.put("store", store);
|
||||
return Map.of("code", 200, "message", "登录成功", "data", data);
|
||||
}
|
||||
|
||||
public Map<String, Object> 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<String, Object> data = new HashMap<>();
|
||||
data.put("user", staff);
|
||||
data.put("store", store);
|
||||
return Map.of("code", 200, "message", "注册成功", "data", data);
|
||||
}
|
||||
|
||||
public Map<String, Object> 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<User> 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<String, Object> updateUser(Map<String, Object> 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);
|
||||
}
|
||||
}
|
||||
42
src/main/resources/application.yml
Normal file
@ -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
|
||||
BIN
uploads/2026/04/01/2ee159a706484affb95e785b4742ca3b.jpg
Normal file
|
After Width: | Height: | Size: 143 KiB |
BIN
uploads/2026/04/01/313b13375c304aa387aa87475feae2c8.jpg
Normal file
|
After Width: | Height: | Size: 143 KiB |
BIN
uploads/2026/04/01/3a4529e8daac48f7a63acd6dbf71a2f9.jpg
Normal file
|
After Width: | Height: | Size: 143 KiB |
BIN
uploads/2026/04/01/3efde5ed9e2b460d8890e7f77f5444aa.jpg
Normal file
|
After Width: | Height: | Size: 143 KiB |
BIN
uploads/2026/04/01/6455dbaa05724d28b9240ce1f35212db.jpg
Normal file
|
After Width: | Height: | Size: 143 KiB |
BIN
uploads/2026/04/01/6e8a6f450b6041d7984cb89092b5c03e.jpg
Normal file
|
After Width: | Height: | Size: 143 KiB |
BIN
uploads/2026/04/01/708f86a8cb5d444380659f13011b3844.jpg
Normal file
|
After Width: | Height: | Size: 143 KiB |
BIN
uploads/2026/04/01/71dfce36906c45c7a33b2e7ff76107f1.jpg
Normal file
|
After Width: | Height: | Size: 143 KiB |
BIN
uploads/2026/04/01/79e7d0e7caa945fbb253dfcad0704970.jpg
Normal file
|
After Width: | Height: | Size: 143 KiB |
BIN
uploads/2026/04/01/97dca4475fe74e489cc41e59715cce24.jpg
Normal file
|
After Width: | Height: | Size: 143 KiB |
BIN
uploads/2026/04/01/a3adb46ccc8d4927be536ab006a6e484.jpg
Normal file
|
After Width: | Height: | Size: 143 KiB |
BIN
uploads/2026/04/01/d48b1611480f419f90f47c6561e4ef85.jpg
Normal file
|
After Width: | Height: | Size: 143 KiB |
BIN
uploads/2026/04/10/09430d9868874ba3b2893b67101d7ad5.jpg
Normal file
|
After Width: | Height: | Size: 143 KiB |
BIN
uploads/2026/04/10/73c2f8330fe34dd394d4f347bef1ef6d.jpg
Normal file
|
After Width: | Height: | Size: 143 KiB |
BIN
uploads/2026/04/10/e85c11305e514239a620292726e49ee2.jpg
Normal file
|
After Width: | Height: | Size: 143 KiB |
BIN
uploads/2026/04/10/fe98df307d304b36810e8aa617296310.jpg
Normal file
|
After Width: | Height: | Size: 143 KiB |
BIN
uploads/2026/04/11/91e6b16f3f7f46759ecddfbbb056cda9.jpg
Normal file
|
After Width: | Height: | Size: 143 KiB |
BIN
uploads/2026/04/11/9e8439362f2e424c900824c309669812.jpg
Normal file
|
After Width: | Height: | Size: 143 KiB |
BIN
uploads/2026/04/11/f14879d02f2a45178f28bd72bbb44998.jpg
Normal file
|
After Width: | Height: | Size: 143 KiB |
BIN
uploads/2026/04/12/2742566684944b6fb52cdc4462ae57b5.jpg
Normal file
|
After Width: | Height: | Size: 143 KiB |
BIN
uploads/2026/04/12/9a0b112e1a50470faaf301b46c6fbe7d.jpg
Normal file
|
After Width: | Height: | Size: 143 KiB |
BIN
uploads/2026/04/12/9c5b51c768fd49fd9bb8fc9e10bd62ac.jpg
Normal file
|
After Width: | Height: | Size: 143 KiB |
BIN
uploads/2026/04/12/a0ae7927c9544db5b293edfd6e2ddd04.jpg
Normal file
|
After Width: | Height: | Size: 143 KiB |
BIN
uploads/2026/04/12/a84696ea7b504e9ab25ed2e9b491bf16.jpg
Normal file
|
After Width: | Height: | Size: 143 KiB |
BIN
uploads/2026/04/12/c125b6c72ba4474696815ee4cc1058d3.jpg
Normal file
|
After Width: | Height: | Size: 143 KiB |
BIN
uploads/2026/04/12/c6f00e5f90844e669da25df62fcfde4b.jpg
Normal file
|
After Width: | Height: | Size: 143 KiB |
BIN
uploads/2026/04/12/e3484bf402354c3389c50a93230fa886.jpg
Normal file
|
After Width: | Height: | Size: 143 KiB |