添加后端代码、数据库文档和FRP配置

This commit is contained in:
2026-01-30 08:59:06 +08:00
parent 37a6b409ed
commit c09ce065fe
210 changed files with 8560 additions and 0 deletions

View File

@@ -0,0 +1,17 @@
package com.gpf.pethospital;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* 爱维宠物医院管理平台主应用
*/
@SpringBootApplication
@MapperScan("com.gpf.pethospital.mapper")
public class PetHospitalApplication {
public static void main(String[] args) {
SpringApplication.run(PetHospitalApplication.class, args);
}
}

View File

@@ -0,0 +1,37 @@
package com.gpf.pethospital.common;
public class ApiResponse<T> {
private final int code;
private final String message;
private final T data;
private ApiResponse(int code, String message, T data) {
this.code = code;
this.message = message;
this.data = data;
}
public static <T> ApiResponse<T> success(T data) {
return new ApiResponse<>(0, "ok", data);
}
public static <T> ApiResponse<T> success(String message, T data) {
return new ApiResponse<>(0, message, data);
}
public static <T> ApiResponse<T> error(int code, String message) {
return new ApiResponse<>(code, message, null);
}
public int getCode() {
return code;
}
public String getMessage() {
return message;
}
public T getData() {
return data;
}
}

View File

@@ -0,0 +1,37 @@
package com.gpf.pethospital.config;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.time.LocalDateTime;
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return interceptor;
}
@Bean
public MetaObjectHandler metaObjectHandler() {
return new MetaObjectHandler() {
@Override
public void insertFill(MetaObject metaObject) {
LocalDateTime now = LocalDateTime.now();
strictInsertFill(metaObject, "createTime", LocalDateTime.class, now);
strictInsertFill(metaObject, "updateTime", LocalDateTime.class, now);
}
@Override
public void updateFill(MetaObject metaObject) {
strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
};
}
}

View File

@@ -0,0 +1,49 @@
package com.gpf.pethospital.config;
import com.gpf.pethospital.security.JwtAuthenticationFilter;
import com.gpf.pethospital.util.JwtUtil;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
@Configuration
@EnableMethodSecurity
public class SecurityConfig {
@Value("${jwt.secret}")
private String jwtSecret;
@Value("${jwt.expiration}")
private long jwtExpiration;
@Bean
public JwtUtil jwtUtil() {
return new JwtUtil(jwtSecret, jwtExpiration);
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http, JwtUtil jwtUtil) throws Exception {
http
.csrf(csrf -> csrf.disable())
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth
.requestMatchers(new AntPathRequestMatcher("/auth/**")).permitAll()
.requestMatchers(new AntPathRequestMatcher("/public/**")).permitAll()
.anyRequest().authenticated()
)
.addFilterBefore(new JwtAuthenticationFilter(jwtUtil), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}

View File

@@ -0,0 +1,17 @@
package com.gpf.pethospital.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("GET", "POST", "PUT", "DELETE", "PATCH")
.allowedHeaders("*")
.maxAge(3600);
}
}

View File

@@ -0,0 +1,72 @@
package com.gpf.pethospital.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.gpf.pethospital.common.ApiResponse;
import com.gpf.pethospital.entity.Appointment;
import com.gpf.pethospital.security.AuthUser;
import com.gpf.pethospital.service.AppointmentService;
import com.gpf.pethospital.util.SecurityUtils;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDateTime;
@RestController
@RequestMapping("/appointments")
public class AppointmentController {
private final AppointmentService appointmentService;
public AppointmentController(AppointmentService appointmentService) {
this.appointmentService = appointmentService;
}
@PostMapping
public ApiResponse<?> create(@RequestBody Appointment appointment) {
AuthUser user = SecurityUtils.currentUser();
if (user != null && "CUSTOMER".equals(user.getRole())) {
appointment.setCustomerId(user.getId());
}
if (appointment.getStatus() == null) {
appointment.setStatus("PENDING");
}
appointmentService.save(appointment);
return ApiResponse.success("created", null);
}
@GetMapping
public ApiResponse<?> list(@RequestParam(defaultValue = "1") long page,
@RequestParam(defaultValue = "10") long size) {
AuthUser user = SecurityUtils.currentUser();
LambdaQueryWrapper<Appointment> wrapper = new LambdaQueryWrapper<>();
if (user != null && "CUSTOMER".equals(user.getRole())) {
wrapper.eq(Appointment::getCustomerId, user.getId());
}
return ApiResponse.success(appointmentService.page(new Page<>(page, size), wrapper));
}
@PreAuthorize("hasAnyRole('ADMIN','DOCTOR')")
@GetMapping("/admin")
public ApiResponse<?> adminList(@RequestParam(defaultValue = "1") long page,
@RequestParam(defaultValue = "10") long size,
@RequestParam(required = false) String status) {
LambdaQueryWrapper<Appointment> wrapper = new LambdaQueryWrapper<>();
if (status != null && !status.isBlank()) {
wrapper.eq(Appointment::getStatus, status);
}
return ApiResponse.success(appointmentService.page(new Page<>(page, size), wrapper));
}
@PreAuthorize("hasAnyRole('ADMIN','DOCTOR')")
@PutMapping("/{id}/status")
public ApiResponse<?> updateStatus(@PathVariable Long id, @RequestParam String status) {
Appointment update = new Appointment();
update.setId(id);
update.setStatus(status);
if ("CANCELLED".equals(status)) {
update.setCancelTime(LocalDateTime.now());
}
appointmentService.updateById(update);
return ApiResponse.success("updated", null);
}
}

View File

@@ -0,0 +1,82 @@
package com.gpf.pethospital.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.gpf.pethospital.common.ApiResponse;
import com.gpf.pethospital.dto.LoginRequest;
import com.gpf.pethospital.dto.RegisterRequest;
import com.gpf.pethospital.entity.User;
import com.gpf.pethospital.service.UserService;
import com.gpf.pethospital.util.JwtUtil;
import jakarta.validation.Valid;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/auth")
public class AuthController {
private final UserService userService;
private final PasswordEncoder passwordEncoder;
private final JwtUtil jwtUtil;
public AuthController(UserService userService, PasswordEncoder passwordEncoder, JwtUtil jwtUtil) {
this.userService = userService;
this.passwordEncoder = passwordEncoder;
this.jwtUtil = jwtUtil;
}
@PostMapping("/register")
public ApiResponse<?> register(@Valid @RequestBody RegisterRequest request) {
if ((request.getPhone() == null || request.getPhone().isBlank())
&& (request.getEmail() == null || request.getEmail().isBlank())) {
return ApiResponse.error(400, "phone or email is required");
}
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(User::getUsername, request.getUsername())
.or()
.eq(request.getPhone() != null && !request.getPhone().isBlank(), User::getPhone, request.getPhone())
.or()
.eq(request.getEmail() != null && !request.getEmail().isBlank(), User::getEmail, request.getEmail());
if (userService.count(wrapper) > 0) {
return ApiResponse.error(409, "user already exists");
}
User user = new User();
user.setUsername(request.getUsername());
user.setPhone(request.getPhone());
user.setEmail(request.getEmail());
user.setPassword(passwordEncoder.encode(request.getPassword()));
user.setRole("CUSTOMER");
user.setStatus(1);
userService.save(user);
return ApiResponse.success("registered", null);
}
@PostMapping("/login")
public ApiResponse<?> login(@Valid @RequestBody LoginRequest request) {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(User::getUsername, request.getAccount())
.or()
.eq(User::getPhone, request.getAccount())
.or()
.eq(User::getEmail, request.getAccount());
User user = userService.getOne(wrapper);
if (user == null || user.getStatus() != null && user.getStatus() == 0) {
return ApiResponse.error(401, "invalid account or disabled");
}
if (!passwordEncoder.matches(request.getPassword(), user.getPassword())) {
return ApiResponse.error(401, "invalid account or password");
}
String token = jwtUtil.generateToken(user.getId(), user.getRole(), user.getUsername());
Map<String, Object> data = new HashMap<>();
data.put("token", token);
data.put("role", user.getRole());
data.put("userId", user.getId());
data.put("username", user.getUsername());
return ApiResponse.success(data);
}
}

View File

@@ -0,0 +1,58 @@
package com.gpf.pethospital.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.gpf.pethospital.common.ApiResponse;
import com.gpf.pethospital.entity.Drug;
import com.gpf.pethospital.service.DrugService;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/drugs")
public class DrugController {
private final DrugService drugService;
public DrugController(DrugService drugService) {
this.drugService = drugService;
}
@PreAuthorize("hasRole('ADMIN')")
@GetMapping
public ApiResponse<?> list(@RequestParam(defaultValue = "1") long page,
@RequestParam(defaultValue = "10") long size,
@RequestParam(required = false) String keyword) {
LambdaQueryWrapper<Drug> wrapper = new LambdaQueryWrapper<>();
if (keyword != null && !keyword.isBlank()) {
wrapper.like(Drug::getName, keyword)
.or()
.like(Drug::getSpecification, keyword);
}
return ApiResponse.success(drugService.page(new Page<>(page, size), wrapper));
}
@PreAuthorize("hasRole('ADMIN')")
@PostMapping
public ApiResponse<?> create(@RequestBody Drug drug) {
if (drug.getStatus() == null) {
drug.setStatus(1);
}
drugService.save(drug);
return ApiResponse.success("created", null);
}
@PreAuthorize("hasRole('ADMIN')")
@PutMapping("/{id}")
public ApiResponse<?> update(@PathVariable Long id, @RequestBody Drug drug) {
drug.setId(id);
drugService.updateById(drug);
return ApiResponse.success("updated", null);
}
@PreAuthorize("hasRole('ADMIN')")
@DeleteMapping("/{id}")
public ApiResponse<?> delete(@PathVariable Long id) {
drugService.removeById(id);
return ApiResponse.success("deleted", null);
}
}

View File

@@ -0,0 +1,50 @@
package com.gpf.pethospital.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.gpf.pethospital.common.ApiResponse;
import com.gpf.pethospital.entity.MedicalRecord;
import com.gpf.pethospital.service.MedicalRecordService;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/medical-records")
public class MedicalRecordController {
private final MedicalRecordService medicalRecordService;
public MedicalRecordController(MedicalRecordService medicalRecordService) {
this.medicalRecordService = medicalRecordService;
}
@PreAuthorize("hasAnyRole('ADMIN','DOCTOR')")
@PostMapping
public ApiResponse<?> create(@RequestBody MedicalRecord record) {
if (record.getStatus() == null) {
record.setStatus("DRAFT");
}
medicalRecordService.save(record);
return ApiResponse.success("created", null);
}
@GetMapping
public ApiResponse<?> list(@RequestParam Long visitId) {
LambdaQueryWrapper<MedicalRecord> wrapper = new LambdaQueryWrapper<MedicalRecord>()
.eq(MedicalRecord::getVisitId, visitId);
return ApiResponse.success(medicalRecordService.list(wrapper));
}
@PreAuthorize("hasAnyRole('ADMIN','DOCTOR')")
@PutMapping("/{id}")
public ApiResponse<?> update(@PathVariable Long id, @RequestBody MedicalRecord record) {
record.setId(id);
medicalRecordService.updateById(record);
return ApiResponse.success("updated", null);
}
@PreAuthorize("hasRole('ADMIN')")
@DeleteMapping("/{id}")
public ApiResponse<?> delete(@PathVariable Long id) {
medicalRecordService.removeById(id);
return ApiResponse.success("deleted", null);
}
}

View File

@@ -0,0 +1,65 @@
package com.gpf.pethospital.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.gpf.pethospital.common.ApiResponse;
import com.gpf.pethospital.dto.ReplyRequest;
import com.gpf.pethospital.entity.Message;
import com.gpf.pethospital.security.AuthUser;
import com.gpf.pethospital.service.MessageService;
import com.gpf.pethospital.util.SecurityUtils;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDateTime;
@RestController
@RequestMapping("/messages")
public class MessageController {
private final MessageService messageService;
public MessageController(MessageService messageService) {
this.messageService = messageService;
}
@PostMapping
public ApiResponse<?> create(@RequestBody Message message) {
AuthUser user = SecurityUtils.currentUser();
if (user != null) {
message.setUserId(user.getId());
message.setUserName(user.getUsername());
}
message.setStatus("PENDING");
messageService.save(message);
return ApiResponse.success("created", null);
}
@PreAuthorize("hasRole('ADMIN')")
@GetMapping("/admin")
public ApiResponse<?> list(@RequestParam(defaultValue = "1") long page,
@RequestParam(defaultValue = "10") long size,
@RequestParam(required = false) String status) {
LambdaQueryWrapper<Message> wrapper = new LambdaQueryWrapper<>();
if (status != null && !status.isBlank()) {
wrapper.eq(Message::getStatus, status);
}
return ApiResponse.success(messageService.page(new Page<>(page, size), wrapper));
}
@PreAuthorize("hasRole('ADMIN')")
@PutMapping("/admin/{id}/reply")
public ApiResponse<?> reply(@PathVariable Long id, @Valid @RequestBody ReplyRequest request) {
AuthUser user = SecurityUtils.currentUser();
Message update = new Message();
update.setId(id);
update.setReply(request.getReply());
update.setStatus("PROCESSED");
update.setReplyTime(LocalDateTime.now());
if (user != null) {
update.setReplyUserId(user.getId());
}
messageService.updateById(update);
return ApiResponse.success("updated", null);
}
}

View File

@@ -0,0 +1,66 @@
package com.gpf.pethospital.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.gpf.pethospital.common.ApiResponse;
import com.gpf.pethospital.entity.Notice;
import com.gpf.pethospital.service.NoticeService;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping
public class NoticeController {
private final NoticeService noticeService;
public NoticeController(NoticeService noticeService) {
this.noticeService = noticeService;
}
@GetMapping("/public/notices")
public ApiResponse<?> publicList(@RequestParam(defaultValue = "1") long page,
@RequestParam(defaultValue = "10") long size) {
LambdaQueryWrapper<Notice> wrapper = new LambdaQueryWrapper<Notice>()
.eq(Notice::getStatus, 1)
.orderByDesc(Notice::getIsTop)
.orderByDesc(Notice::getCreateTime);
return ApiResponse.success(noticeService.page(new Page<>(page, size), wrapper));
}
@GetMapping("/public/notices/{id}")
public ApiResponse<?> publicDetail(@PathVariable Long id) {
return ApiResponse.success(noticeService.getById(id));
}
@PreAuthorize("hasRole('ADMIN')")
@GetMapping("/notices")
public ApiResponse<?> list(@RequestParam(defaultValue = "1") long page,
@RequestParam(defaultValue = "10") long size) {
return ApiResponse.success(noticeService.page(new Page<>(page, size)));
}
@PreAuthorize("hasRole('ADMIN')")
@PostMapping("/notices")
public ApiResponse<?> create(@RequestBody Notice notice) {
if (notice.getStatus() == null) {
notice.setStatus(1);
}
noticeService.save(notice);
return ApiResponse.success("created", null);
}
@PreAuthorize("hasRole('ADMIN')")
@PutMapping("/notices/{id}")
public ApiResponse<?> update(@PathVariable Long id, @RequestBody Notice notice) {
notice.setId(id);
noticeService.updateById(notice);
return ApiResponse.success("updated", null);
}
@PreAuthorize("hasRole('ADMIN')")
@DeleteMapping("/notices/{id}")
public ApiResponse<?> delete(@PathVariable Long id) {
noticeService.removeById(id);
return ApiResponse.success("deleted", null);
}
}

View File

@@ -0,0 +1,53 @@
package com.gpf.pethospital.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.gpf.pethospital.common.ApiResponse;
import com.gpf.pethospital.entity.Order;
import com.gpf.pethospital.security.AuthUser;
import com.gpf.pethospital.service.OrderService;
import com.gpf.pethospital.util.SecurityUtils;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/orders")
public class OrderController {
private final OrderService orderService;
public OrderController(OrderService orderService) {
this.orderService = orderService;
}
@PostMapping
public ApiResponse<?> create(@RequestBody Order order) {
AuthUser user = SecurityUtils.currentUser();
if (user != null && "CUSTOMER".equals(user.getRole())) {
order.setCustomerId(user.getId());
}
if (order.getStatus() == null) {
order.setStatus("UNPAID");
}
orderService.save(order);
return ApiResponse.success("created", null);
}
@GetMapping
public ApiResponse<?> list(@RequestParam(defaultValue = "1") long page,
@RequestParam(defaultValue = "10") long size) {
AuthUser user = SecurityUtils.currentUser();
LambdaQueryWrapper<Order> wrapper = new LambdaQueryWrapper<>();
if (user != null && "CUSTOMER".equals(user.getRole())) {
wrapper.eq(Order::getCustomerId, user.getId());
}
return ApiResponse.success(orderService.page(new Page<>(page, size), wrapper));
}
@PreAuthorize("hasAnyRole('ADMIN','DOCTOR')")
@PutMapping("/{id}")
public ApiResponse<?> update(@PathVariable Long id, @RequestBody Order order) {
order.setId(id);
orderService.updateById(order);
return ApiResponse.success("updated", null);
}
}

View File

@@ -0,0 +1,79 @@
package com.gpf.pethospital.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.gpf.pethospital.common.ApiResponse;
import com.gpf.pethospital.entity.Pet;
import com.gpf.pethospital.security.AuthUser;
import com.gpf.pethospital.service.PetService;
import com.gpf.pethospital.util.SecurityUtils;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/pets")
public class PetController {
private final PetService petService;
public PetController(PetService petService) {
this.petService = petService;
}
@GetMapping
public ApiResponse<?> list(@RequestParam(defaultValue = "1") long page,
@RequestParam(defaultValue = "10") long size,
@RequestParam(required = false) Long ownerId) {
AuthUser user = SecurityUtils.currentUser();
LambdaQueryWrapper<Pet> wrapper = new LambdaQueryWrapper<>();
if (user != null && "CUSTOMER".equals(user.getRole())) {
wrapper.eq(Pet::getOwnerId, user.getId());
} else if (ownerId != null) {
wrapper.eq(Pet::getOwnerId, ownerId);
}
return ApiResponse.success(petService.page(new Page<>(page, size), wrapper));
}
@PostMapping
public ApiResponse<?> create(@RequestBody Pet pet) {
AuthUser user = SecurityUtils.currentUser();
if (user != null && "CUSTOMER".equals(user.getRole())) {
pet.setOwnerId(user.getId());
}
petService.save(pet);
return ApiResponse.success("created", null);
}
@PutMapping("/{id}")
public ApiResponse<?> update(@PathVariable Long id, @RequestBody Pet pet) {
AuthUser user = SecurityUtils.currentUser();
if (user != null && "CUSTOMER".equals(user.getRole())) {
Pet existing = petService.getById(id);
if (existing == null || !existing.getOwnerId().equals(user.getId())) {
return ApiResponse.error(403, "forbidden");
}
}
pet.setId(id);
petService.updateById(pet);
return ApiResponse.success("updated", null);
}
@DeleteMapping("/{id}")
public ApiResponse<?> delete(@PathVariable Long id) {
AuthUser user = SecurityUtils.currentUser();
if (user != null && "CUSTOMER".equals(user.getRole())) {
Pet existing = petService.getById(id);
if (existing == null || !existing.getOwnerId().equals(user.getId())) {
return ApiResponse.error(403, "forbidden");
}
}
petService.removeById(id);
return ApiResponse.success("deleted", null);
}
@PreAuthorize("hasRole('ADMIN')")
@GetMapping("/admin/all")
public ApiResponse<?> adminList(@RequestParam(defaultValue = "1") long page,
@RequestParam(defaultValue = "10") long size) {
return ApiResponse.success(petService.page(new Page<>(page, size)));
}
}

View File

@@ -0,0 +1,54 @@
package com.gpf.pethospital.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.gpf.pethospital.common.ApiResponse;
import com.gpf.pethospital.entity.Prescription;
import com.gpf.pethospital.security.AuthUser;
import com.gpf.pethospital.service.PrescriptionService;
import com.gpf.pethospital.util.SecurityUtils;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/prescriptions")
public class PrescriptionController {
private final PrescriptionService prescriptionService;
public PrescriptionController(PrescriptionService prescriptionService) {
this.prescriptionService = prescriptionService;
}
@PreAuthorize("hasAnyRole('ADMIN','DOCTOR')")
@PostMapping
public ApiResponse<?> create(@RequestBody Prescription prescription) {
if (prescription.getStatus() == null) {
prescription.setStatus("DRAFT");
}
prescriptionService.save(prescription);
return ApiResponse.success("created", null);
}
@GetMapping
public ApiResponse<?> list(@RequestParam(defaultValue = "1") long page,
@RequestParam(defaultValue = "10") long size,
@RequestParam(required = false) Long visitId) {
AuthUser user = SecurityUtils.currentUser();
LambdaQueryWrapper<Prescription> wrapper = new LambdaQueryWrapper<>();
if (visitId != null) {
wrapper.eq(Prescription::getVisitId, visitId);
}
if (user != null && "DOCTOR".equals(user.getRole())) {
wrapper.eq(Prescription::getDoctorId, user.getId());
}
return ApiResponse.success(prescriptionService.page(new Page<>(page, size), wrapper));
}
@PreAuthorize("hasAnyRole('ADMIN','DOCTOR')")
@PutMapping("/{id}")
public ApiResponse<?> update(@PathVariable Long id, @RequestBody Prescription prescription) {
prescription.setId(id);
prescriptionService.updateById(prescription);
return ApiResponse.success("updated", null);
}
}

View File

@@ -0,0 +1,47 @@
package com.gpf.pethospital.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.gpf.pethospital.common.ApiResponse;
import com.gpf.pethospital.entity.PrescriptionItem;
import com.gpf.pethospital.service.PrescriptionItemService;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/prescription-items")
public class PrescriptionItemController {
private final PrescriptionItemService prescriptionItemService;
public PrescriptionItemController(PrescriptionItemService prescriptionItemService) {
this.prescriptionItemService = prescriptionItemService;
}
@GetMapping
public ApiResponse<?> list(@RequestParam Long prescriptionId) {
LambdaQueryWrapper<PrescriptionItem> wrapper = new LambdaQueryWrapper<PrescriptionItem>()
.eq(PrescriptionItem::getPrescriptionId, prescriptionId);
return ApiResponse.success(prescriptionItemService.list(wrapper));
}
@PreAuthorize("hasAnyRole('ADMIN','DOCTOR')")
@PostMapping
public ApiResponse<?> create(@RequestBody PrescriptionItem item) {
prescriptionItemService.save(item);
return ApiResponse.success("created", null);
}
@PreAuthorize("hasAnyRole('ADMIN','DOCTOR')")
@PutMapping("/{id}")
public ApiResponse<?> update(@PathVariable Long id, @RequestBody PrescriptionItem item) {
item.setId(id);
prescriptionItemService.updateById(item);
return ApiResponse.success("updated", null);
}
@PreAuthorize("hasAnyRole('ADMIN','DOCTOR')")
@DeleteMapping("/{id}")
public ApiResponse<?> delete(@PathVariable Long id) {
prescriptionItemService.removeById(id);
return ApiResponse.success("deleted", null);
}
}

View File

@@ -0,0 +1,58 @@
package com.gpf.pethospital.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.gpf.pethospital.common.ApiResponse;
import com.gpf.pethospital.entity.Report;
import com.gpf.pethospital.security.AuthUser;
import com.gpf.pethospital.service.ReportService;
import com.gpf.pethospital.util.SecurityUtils;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/reports")
public class ReportController {
private final ReportService reportService;
public ReportController(ReportService reportService) {
this.reportService = reportService;
}
@PreAuthorize("hasAnyRole('ADMIN','DOCTOR')")
@PostMapping
public ApiResponse<?> create(@RequestBody Report report) {
reportService.save(report);
return ApiResponse.success("created", null);
}
@GetMapping
public ApiResponse<?> list(@RequestParam(defaultValue = "1") long page,
@RequestParam(defaultValue = "10") long size,
@RequestParam(required = false) Long petId) {
AuthUser user = SecurityUtils.currentUser();
LambdaQueryWrapper<Report> wrapper = new LambdaQueryWrapper<>();
if (petId != null) {
wrapper.eq(Report::getPetId, petId);
}
if (user != null && "DOCTOR".equals(user.getRole())) {
wrapper.eq(Report::getDoctorId, user.getId());
}
return ApiResponse.success(reportService.page(new Page<>(page, size), wrapper));
}
@PreAuthorize("hasAnyRole('ADMIN','DOCTOR')")
@PutMapping("/{id}")
public ApiResponse<?> update(@PathVariable Long id, @RequestBody Report report) {
report.setId(id);
reportService.updateById(report);
return ApiResponse.success("updated", null);
}
@PreAuthorize("hasRole('ADMIN')")
@DeleteMapping("/{id}")
public ApiResponse<?> delete(@PathVariable Long id) {
reportService.removeById(id);
return ApiResponse.success("deleted", null);
}
}

View File

@@ -0,0 +1,64 @@
package com.gpf.pethospital.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.gpf.pethospital.common.ApiResponse;
import com.gpf.pethospital.entity.Order;
import com.gpf.pethospital.entity.User;
import com.gpf.pethospital.service.AppointmentService;
import com.gpf.pethospital.service.OrderService;
import com.gpf.pethospital.service.PetService;
import com.gpf.pethospital.service.UserService;
import com.gpf.pethospital.service.VisitService;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/admin/stats")
public class StatsController {
private final OrderService orderService;
private final AppointmentService appointmentService;
private final VisitService visitService;
private final PetService petService;
private final UserService userService;
public StatsController(OrderService orderService,
AppointmentService appointmentService,
VisitService visitService,
PetService petService,
UserService userService) {
this.orderService = orderService;
this.appointmentService = appointmentService;
this.visitService = visitService;
this.petService = petService;
this.userService = userService;
}
@PreAuthorize("hasRole('ADMIN')")
@GetMapping
public ApiResponse<?> summary() {
Map<String, Object> data = new HashMap<>();
data.put("orders", orderService.count());
data.put("appointments", appointmentService.count());
data.put("visits", visitService.count());
data.put("pets", petService.count());
data.put("customers", userService.count(new LambdaQueryWrapper<User>().eq(User::getRole, "CUSTOMER")));
QueryWrapper<Order> wrapper = new QueryWrapper<>();
wrapper.select("SUM(amount) AS total");
List<Map<String, Object>> result = orderService.listMaps(wrapper);
BigDecimal total = BigDecimal.ZERO;
if (!result.isEmpty() && result.get(0).get("total") != null) {
total = new BigDecimal(result.get(0).get("total").toString());
}
data.put("orderAmountTotal", total);
return ApiResponse.success(data);
}
}

View File

@@ -0,0 +1,52 @@
package com.gpf.pethospital.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.gpf.pethospital.common.ApiResponse;
import com.gpf.pethospital.entity.Drug;
import com.gpf.pethospital.entity.StockIn;
import com.gpf.pethospital.service.DrugService;
import com.gpf.pethospital.service.StockInService;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/stock-in")
public class StockInController {
private final StockInService stockInService;
private final DrugService drugService;
public StockInController(StockInService stockInService, DrugService drugService) {
this.stockInService = stockInService;
this.drugService = drugService;
}
@PreAuthorize("hasRole('ADMIN')")
@GetMapping
public ApiResponse<?> list(@RequestParam(defaultValue = "1") long page,
@RequestParam(defaultValue = "10") long size,
@RequestParam(required = false) Long drugId) {
LambdaQueryWrapper<StockIn> wrapper = new LambdaQueryWrapper<>();
if (drugId != null) {
wrapper.eq(StockIn::getDrugId, drugId);
}
return ApiResponse.success(stockInService.page(new Page<>(page, size), wrapper));
}
@PreAuthorize("hasRole('ADMIN')")
@PostMapping
@Transactional
public ApiResponse<?> create(@RequestBody StockIn stockIn) {
stockInService.save(stockIn);
if (stockIn.getDrugId() != null && stockIn.getQuantity() != null) {
Drug drug = drugService.getById(stockIn.getDrugId());
if (drug != null) {
int current = drug.getStock() == null ? 0 : drug.getStock();
drug.setStock(current + stockIn.getQuantity());
drugService.updateById(drug);
}
}
return ApiResponse.success("created", null);
}
}

View File

@@ -0,0 +1,52 @@
package com.gpf.pethospital.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.gpf.pethospital.common.ApiResponse;
import com.gpf.pethospital.entity.Drug;
import com.gpf.pethospital.entity.StockOut;
import com.gpf.pethospital.service.DrugService;
import com.gpf.pethospital.service.StockOutService;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/stock-out")
public class StockOutController {
private final StockOutService stockOutService;
private final DrugService drugService;
public StockOutController(StockOutService stockOutService, DrugService drugService) {
this.stockOutService = stockOutService;
this.drugService = drugService;
}
@PreAuthorize("hasRole('ADMIN')")
@GetMapping
public ApiResponse<?> list(@RequestParam(defaultValue = "1") long page,
@RequestParam(defaultValue = "10") long size,
@RequestParam(required = false) Long drugId) {
LambdaQueryWrapper<StockOut> wrapper = new LambdaQueryWrapper<>();
if (drugId != null) {
wrapper.eq(StockOut::getDrugId, drugId);
}
return ApiResponse.success(stockOutService.page(new Page<>(page, size), wrapper));
}
@PreAuthorize("hasRole('ADMIN')")
@PostMapping
@Transactional
public ApiResponse<?> create(@RequestBody StockOut stockOut) {
stockOutService.save(stockOut);
if (stockOut.getDrugId() != null && stockOut.getQuantity() != null) {
Drug drug = drugService.getById(stockOut.getDrugId());
if (drug != null) {
int current = drug.getStock() == null ? 0 : drug.getStock();
drug.setStock(Math.max(0, current - stockOut.getQuantity()));
drugService.updateById(drug);
}
}
return ApiResponse.success("created", null);
}
}

View File

@@ -0,0 +1,115 @@
package com.gpf.pethospital.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.gpf.pethospital.common.ApiResponse;
import com.gpf.pethospital.entity.User;
import com.gpf.pethospital.security.AuthUser;
import com.gpf.pethospital.service.UserService;
import com.gpf.pethospital.util.SecurityUtils;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/users")
public class UserController {
private final UserService userService;
private final PasswordEncoder passwordEncoder;
public UserController(UserService userService, PasswordEncoder passwordEncoder) {
this.userService = userService;
this.passwordEncoder = passwordEncoder;
}
@GetMapping("/me")
public ApiResponse<?> me() {
AuthUser authUser = SecurityUtils.currentUser();
if (authUser == null) {
return ApiResponse.error(401, "unauthorized");
}
User user = userService.getById(authUser.getId());
if (user != null) {
user.setPassword(null);
}
return ApiResponse.success(user);
}
@PutMapping("/me")
public ApiResponse<?> updateMe(@RequestBody User payload) {
AuthUser authUser = SecurityUtils.currentUser();
if (authUser == null) {
return ApiResponse.error(401, "unauthorized");
}
User update = new User();
update.setId(authUser.getId());
update.setUsername(payload.getUsername());
update.setPhone(payload.getPhone());
update.setEmail(payload.getEmail());
update.setAvatar(payload.getAvatar());
userService.updateById(update);
return ApiResponse.success("updated", null);
}
@PreAuthorize("hasRole('ADMIN')")
@GetMapping
public ApiResponse<?> list(@RequestParam(defaultValue = "1") long page,
@RequestParam(defaultValue = "10") long size,
@RequestParam(required = false) String role) {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
if (role != null && !role.isBlank()) {
wrapper.eq(User::getRole, role);
}
Page<User> result = userService.page(new Page<>(page, size), wrapper);
result.getRecords().forEach(u -> u.setPassword(null));
return ApiResponse.success(result);
}
@PreAuthorize("hasRole('ADMIN')")
@PostMapping
public ApiResponse<?> create(@RequestBody User user) {
if (user.getPassword() == null || user.getPassword().isBlank()) {
return ApiResponse.error(400, "password required");
}
user.setPassword(passwordEncoder.encode(user.getPassword()));
if (user.getStatus() == null) {
user.setStatus(1);
}
userService.save(user);
return ApiResponse.success("created", null);
}
@PreAuthorize("hasRole('ADMIN')")
@PutMapping("/{id}/status")
public ApiResponse<?> updateStatus(@PathVariable Long id, @RequestParam Integer status) {
User update = new User();
update.setId(id);
update.setStatus(status);
userService.updateById(update);
return ApiResponse.success("updated", null);
}
@PreAuthorize("hasRole('ADMIN')")
@PutMapping("/{id}/reset-password")
public ApiResponse<?> resetPassword(@PathVariable Long id, @RequestParam String newPassword) {
User update = new User();
update.setId(id);
update.setPassword(passwordEncoder.encode(newPassword));
userService.updateById(update);
return ApiResponse.success("updated", null);
}
@PreAuthorize("hasRole('ADMIN')")
@GetMapping("/stats")
public ApiResponse<?> stats() {
Map<String, Object> data = new HashMap<>();
data.put("total", userService.count());
data.put("admins", userService.count(new LambdaQueryWrapper<User>().eq(User::getRole, "ADMIN")));
data.put("doctors", userService.count(new LambdaQueryWrapper<User>().eq(User::getRole, "DOCTOR")));
data.put("customers", userService.count(new LambdaQueryWrapper<User>().eq(User::getRole, "CUSTOMER")));
return ApiResponse.success(data);
}
}

View File

@@ -0,0 +1,59 @@
package com.gpf.pethospital.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.gpf.pethospital.common.ApiResponse;
import com.gpf.pethospital.entity.Pet;
import com.gpf.pethospital.entity.VaccineRecord;
import com.gpf.pethospital.security.AuthUser;
import com.gpf.pethospital.service.PetService;
import com.gpf.pethospital.service.VaccineRecordService;
import com.gpf.pethospital.util.SecurityUtils;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/vaccines")
public class VaccineRecordController {
private final VaccineRecordService vaccineRecordService;
private final PetService petService;
public VaccineRecordController(VaccineRecordService vaccineRecordService, PetService petService) {
this.vaccineRecordService = vaccineRecordService;
this.petService = petService;
}
@GetMapping
public ApiResponse<?> list(@RequestParam Long petId,
@RequestParam(defaultValue = "1") long page,
@RequestParam(defaultValue = "10") long size) {
AuthUser user = SecurityUtils.currentUser();
if (user != null && "CUSTOMER".equals(user.getRole())) {
Pet pet = petService.getById(petId);
if (pet == null || !pet.getOwnerId().equals(user.getId())) {
return ApiResponse.error(403, "forbidden");
}
}
LambdaQueryWrapper<VaccineRecord> wrapper = new LambdaQueryWrapper<VaccineRecord>()
.eq(VaccineRecord::getPetId, petId);
return ApiResponse.success(vaccineRecordService.page(new Page<>(page, size), wrapper));
}
@PostMapping
public ApiResponse<?> create(@RequestBody VaccineRecord record) {
vaccineRecordService.save(record);
return ApiResponse.success("created", null);
}
@PutMapping("/{id}")
public ApiResponse<?> update(@PathVariable Long id, @RequestBody VaccineRecord record) {
record.setId(id);
vaccineRecordService.updateById(record);
return ApiResponse.success("updated", null);
}
@DeleteMapping("/{id}")
public ApiResponse<?> delete(@PathVariable Long id) {
vaccineRecordService.removeById(id);
return ApiResponse.success("deleted", null);
}
}

View File

@@ -0,0 +1,57 @@
package com.gpf.pethospital.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.gpf.pethospital.common.ApiResponse;
import com.gpf.pethospital.entity.Visit;
import com.gpf.pethospital.security.AuthUser;
import com.gpf.pethospital.service.VisitService;
import com.gpf.pethospital.util.SecurityUtils;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/visits")
public class VisitController {
private final VisitService visitService;
public VisitController(VisitService visitService) {
this.visitService = visitService;
}
@PreAuthorize("hasAnyRole('ADMIN','DOCTOR')")
@PostMapping
public ApiResponse<?> create(@RequestBody Visit visit) {
if (visit.getStatus() == null) {
visit.setStatus("IN_PROGRESS");
}
if (visit.getPaymentStatus() == null) {
visit.setPaymentStatus("UNPAID");
}
visitService.save(visit);
return ApiResponse.success("created", null);
}
@GetMapping
public ApiResponse<?> list(@RequestParam(defaultValue = "1") long page,
@RequestParam(defaultValue = "10") long size) {
AuthUser user = SecurityUtils.currentUser();
LambdaQueryWrapper<Visit> wrapper = new LambdaQueryWrapper<>();
if (user != null) {
if ("CUSTOMER".equals(user.getRole())) {
wrapper.eq(Visit::getCustomerId, user.getId());
} else if ("DOCTOR".equals(user.getRole())) {
wrapper.eq(Visit::getDoctorId, user.getId());
}
}
return ApiResponse.success(visitService.page(new Page<>(page, size), wrapper));
}
@PreAuthorize("hasAnyRole('ADMIN','DOCTOR')")
@PutMapping("/{id}")
public ApiResponse<?> update(@PathVariable Long id, @RequestBody Visit visit) {
visit.setId(id);
visitService.updateById(visit);
return ApiResponse.success("updated", null);
}
}

View File

@@ -0,0 +1,26 @@
package com.gpf.pethospital.dto;
import jakarta.validation.constraints.NotBlank;
public class LoginRequest {
@NotBlank
private String account;
@NotBlank
private String password;
public String getAccount() {
return account;
}
public void setAccount(String account) {
this.account = account;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}

View File

@@ -0,0 +1,46 @@
package com.gpf.pethospital.dto;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
public class RegisterRequest {
@NotBlank
private String username;
private String phone;
@Email
private String email;
@NotBlank
private String password;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}

View File

@@ -0,0 +1,16 @@
package com.gpf.pethospital.dto;
import jakarta.validation.constraints.NotBlank;
public class ReplyRequest {
@NotBlank
private String reply;
public String getReply() {
return reply;
}
public void setReply(String reply) {
this.reply = reply;
}
}

View File

@@ -0,0 +1,83 @@
package com.gpf.pethospital.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import lombok.experimental.Accessors;
import java.time.LocalDate;
import java.time.LocalDateTime;
/**
* 门诊预约实体
*/
@Data
@Accessors(chain = true)
@TableName("appointment")
public class Appointment {
@TableId(type = IdType.AUTO)
private Long id;
/**
* 顾客ID
*/
private Long customerId;
/**
* 宠物ID
*/
private Long petId;
/**
* 预约医生ID可为空表示不指定医生
*/
private Long doctorId;
/**
* 科室
*/
private String department;
/**
* 预约日期
*/
private LocalDate appointmentDate;
/**
* 预约时段例如09:00-10:00
*/
private String timeSlot;
/**
* 预约状态PENDING-待确认, CONFIRMED-已确认, ARRIVED-已到诊, CANCELLED-已取消, NO_SHOW-爽约
*/
private String status;
/**
* 备注
*/
private String remark;
/**
* 取消时间
*/
private LocalDateTime cancelTime;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
/**
* 删除标记0-未删除, 1-已删除
*/
@TableLogic
private Integer deleted;
}

View File

@@ -0,0 +1,93 @@
package com.gpf.pethospital.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import lombok.experimental.Accessors;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* 药品实体
*/
@Data
@Accessors(chain = true)
@TableName("drug")
public class Drug {
@TableId(type = IdType.AUTO)
private Long id;
/**
* 药品名称
*/
private String name;
/**
* 规格
*/
private String specification;
/**
* 单位(如:片、盒、瓶)
*/
private String unit;
/**
* 当前库存数量
*/
private Integer stock;
/**
* 预警阈值
*/
private Integer alertThreshold;
/**
* 进价
*/
private BigDecimal purchasePrice;
/**
* 售价
*/
private BigDecimal salePrice;
/**
* 生产厂家
*/
private String manufacturer;
/**
* 批准文号
*/
private String approvalNumber;
/**
* 有效期
*/
private LocalDateTime expiryDate;
/**
* 状态0-禁用, 1-启用
*/
private Integer status;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
/**
* 删除标记0-未删除, 1-已删除
*/
@TableLogic
private Integer deleted;
}

View File

@@ -0,0 +1,82 @@
package com.gpf.pethospital.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import lombok.experimental.Accessors;
import java.time.LocalDateTime;
/**
* 病历实体
*/
@Data
@Accessors(chain = true)
@TableName("medical_record")
public class MedicalRecord {
@TableId(type = IdType.AUTO)
private Long id;
/**
* 就诊记录ID
*/
private Long visitId;
/**
* 主诉
*/
private String chiefComplaint;
/**
* 现病史
*/
private String presentIllness;
/**
* 体格检查
*/
private String physicalExamination;
/**
* 检查结果
*/
private String examinationResults;
/**
* 诊断结论
*/
private String diagnosis;
/**
* 治疗方案
*/
private String treatmentPlan;
/**
* 医嘱
*/
private String advice;
/**
* 状态DRAFT-草稿, COMPLETED-已完成
*/
private String status;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
/**
* 删除标记0-未删除, 1-已删除
*/
@TableLogic
private Integer deleted;
}

View File

@@ -0,0 +1,82 @@
package com.gpf.pethospital.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import lombok.experimental.Accessors;
import java.time.LocalDateTime;
/**
* 留言实体
*/
@Data
@Accessors(chain = true)
@TableName("message")
public class Message {
@TableId(type = IdType.AUTO)
private Long id;
/**
* 留言人ID
*/
private Long userId;
/**
* 留言人姓名
*/
private String userName;
/**
* 联系方式
*/
private String contact;
/**
* 标题
*/
private String title;
/**
* 内容
*/
private String content;
/**
* 处理状态PENDING-待处理, PROCESSED-已处理
*/
private String status;
/**
* 回复内容
*/
private String reply;
/**
* 回复时间
*/
private LocalDateTime replyTime;
/**
* 回复者ID
*/
private Long replyUserId;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
/**
* 删除标记0-未删除, 1-已删除
*/
@TableLogic
private Integer deleted;
}

View File

@@ -0,0 +1,62 @@
package com.gpf.pethospital.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import lombok.experimental.Accessors;
import java.time.LocalDateTime;
/**
* 公告实体
*/
@Data
@Accessors(chain = true)
@TableName("notice")
public class Notice {
@TableId(type = IdType.AUTO)
private Long id;
/**
* 标题
*/
private String title;
/**
* 内容
*/
private String content;
/**
* 是否置顶0-否, 1-是
*/
private Integer isTop;
/**
* 状态0-下架, 1-上架
*/
private Integer status;
/**
* 发布者ID
*/
private Long publisherId;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
/**
* 删除标记0-未删除, 1-已删除
*/
@TableLogic
private Integer deleted;
}

View File

@@ -0,0 +1,78 @@
package com.gpf.pethospital.entity;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.IdType;
import lombok.Data;
import lombok.experimental.Accessors;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* 订单实体
*/
@Data
@Accessors(chain = true)
@TableName("order_info")
public class Order {
@TableId(type = IdType.AUTO)
private Long id;
/**
* 就诊记录ID
*/
private Long visitId;
/**
* 顾客ID
*/
private Long customerId;
/**
* 订单金额
*/
private BigDecimal amount;
/**
* 订单状态UNPAID-未支付, PAID-已支付, CANCELLED-已取消, REFUNDING-退款中, REFUNDED-已退款
*/
private String status;
/**
* 支付方式OFFLINE-线下支付, ALIPAY-支付宝, WECHAT-微信支付
*/
private String paymentMethod;
/**
* 支付时间
*/
private LocalDateTime paymentTime;
/**
* 备注
*/
private String remark;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
/**
* 删除标记0-未删除, 1-已删除
*/
@TableLogic
private Integer deleted;
}

View File

@@ -0,0 +1,78 @@
package com.gpf.pethospital.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import lombok.experimental.Accessors;
import java.time.LocalDate;
import java.time.LocalDateTime;
/**
* 宠物实体
*/
@Data
@Accessors(chain = true)
@TableName("pet")
public class Pet {
@TableId(type = IdType.AUTO)
private Long id;
/**
* 宠物主人ID
*/
private Long ownerId;
/**
* 宠物名称
*/
private String name;
/**
* 品种
*/
private String breed;
/**
* 性别MALE-雄性, FEMALE-雌性
*/
private String gender;
/**
* 生日
*/
private LocalDate birthday;
/**
* 体重(kg)
*/
private Double weight;
/**
* 照片URL
*/
private String photo;
/**
* 备注
*/
private String remark;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
/**
* 删除标记0-未删除, 1-已删除
*/
@TableLogic
private Integer deleted;
}

View File

@@ -0,0 +1,57 @@
package com.gpf.pethospital.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import lombok.experimental.Accessors;
import java.time.LocalDateTime;
/**
* 处方实体
*/
@Data
@Accessors(chain = true)
@TableName("prescription")
public class Prescription {
@TableId(type = IdType.AUTO)
private Long id;
/**
* 就诊记录ID
*/
private Long visitId;
/**
* 医生ID
*/
private Long doctorId;
/**
* 处方状态DRAFT-草稿, SUBMITTED-已提交, ISSUED-已发药, VOIDED-已作废
*/
private String status;
/**
* 备注
*/
private String remark;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
/**
* 删除标记0-未删除, 1-已删除
*/
@TableLogic
private Integer deleted;
}

View File

@@ -0,0 +1,83 @@
package com.gpf.pethospital.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import lombok.experimental.Accessors;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* 处方明细实体
*/
@Data
@Accessors(chain = true)
@TableName("prescription_item")
public class PrescriptionItem {
@TableId(type = IdType.AUTO)
private Long id;
/**
* 处方ID
*/
private Long prescriptionId;
/**
* 药品ID
*/
private Long drugId;
/**
* 药品名称(冗余字段,方便查询)
*/
private String drugName;
/**
* 规格
*/
private String specification;
/**
* 数量
*/
private Integer quantity;
/**
* 用法用量
*/
private String usage;
/**
* 用药天数
*/
private Integer days;
/**
* 单价
*/
private BigDecimal unitPrice;
/**
* 小计
*/
private BigDecimal subtotal;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
/**
* 删除标记0-未删除, 1-已删除
*/
@TableLogic
private Integer deleted;
}

View File

@@ -0,0 +1,72 @@
package com.gpf.pethospital.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import lombok.experimental.Accessors;
import java.time.LocalDateTime;
/**
* 检查报告实体
*/
@Data
@Accessors(chain = true)
@TableName("report")
public class Report {
@TableId(type = IdType.AUTO)
private Long id;
/**
* 就诊记录ID
*/
private Long visitId;
/**
* 宠物ID
*/
private Long petId;
/**
* 报告类型血常规、X光、B超等
*/
private String type;
/**
* 报告名称
*/
private String title;
/**
* 报告内容摘要
*/
private String summary;
/**
* 附件URL图片或PDF
*/
private String attachmentUrl;
/**
* 检查医生ID
*/
private Long doctorId;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
/**
* 删除标记0-未删除, 1-已删除
*/
@TableLogic
private Integer deleted;
}

View File

@@ -0,0 +1,67 @@
package com.gpf.pethospital.entity;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.IdType;
import lombok.Data;
import lombok.experimental.Accessors;
import java.time.LocalDateTime;
/**
* 入库流水实体
*/
@Data
@Accessors(chain = true)
@TableName("stock_in")
public class StockIn {
@TableId(type = IdType.AUTO)
private Long id;
/**
* 药品ID
*/
private Long drugId;
/**
* 入库数量
*/
private Integer quantity;
/**
* 经办人ID
*/
private Long operatorId;
/**
* 入库时间
*/
private LocalDateTime stockInTime;
/**
* 备注
*/
private String remark;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
/**
* 删除标记0-未删除, 1-已删除
*/
@TableLogic
private Integer deleted;
}

View File

@@ -0,0 +1,72 @@
package com.gpf.pethospital.entity;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.IdType;
import lombok.Data;
import lombok.experimental.Accessors;
import java.time.LocalDateTime;
/**
* 出库/消耗流水实体
*/
@Data
@Accessors(chain = true)
@TableName("stock_out")
public class StockOut {
@TableId(type = IdType.AUTO)
private Long id;
/**
* 药品ID
*/
private Long drugId;
/**
* 处方ID可为空
*/
private Long prescriptionId;
/**
* 出库数量
*/
private Integer quantity;
/**
* 经办人ID
*/
private Long operatorId;
/**
* 出库时间
*/
private LocalDateTime stockOutTime;
/**
* 备注
*/
private String remark;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
/**
* 删除标记0-未删除, 1-已删除
*/
@TableLogic
private Integer deleted;
}

View File

@@ -0,0 +1,72 @@
package com.gpf.pethospital.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import lombok.experimental.Accessors;
import java.time.LocalDateTime;
/**
* 用户实体
*/
@Data
@Accessors(chain = true)
@TableName("`user`")
public class User {
@TableId(type = IdType.AUTO)
private Long id;
/**
* 用户名/姓名
*/
private String username;
/**
* 手机号
*/
private String phone;
/**
* 邮箱
*/
private String email;
/**
* 密码(加密后)
*/
private String password;
/**
* 角色ADMIN-管理员, DOCTOR-医生, CUSTOMER-顾客
*/
private String role;
/**
* 状态0-禁用, 1-启用
*/
private Integer status;
/**
* 头像URL
*/
private String avatar;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
/**
* 删除标记0-未删除, 1-已删除
*/
@TableLogic
private Integer deleted;
}

View File

@@ -0,0 +1,68 @@
package com.gpf.pethospital.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import lombok.experimental.Accessors;
import java.time.LocalDate;
import java.time.LocalDateTime;
/**
* 疫苗/驱虫记录实体
*/
@Data
@Accessors(chain = true)
@TableName("vaccine_record")
public class VaccineRecord {
@TableId(type = IdType.AUTO)
private Long id;
/**
* 宠物ID
*/
private Long petId;
/**
* 记录类型VACCINE-疫苗, DEWORMING-驱虫
*/
private String type;
/**
* 项目名称(如:狂犬疫苗、体内驱虫等)
*/
private String itemName;
/**
* 执行日期
*/
private LocalDate executeDate;
/**
* 下次提醒日期
*/
private LocalDate nextReminderDate;
/**
* 备注
*/
private String remark;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
/**
* 删除标记0-未删除, 1-已删除
*/
@TableLogic
private Integer deleted;
}

View File

@@ -0,0 +1,93 @@
package com.gpf.pethospital.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import lombok.experimental.Accessors;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* 就诊记录实体
*/
@Data
@Accessors(chain = true)
@TableName("visit")
public class Visit {
@TableId(type = IdType.AUTO)
private Long id;
/**
* 预约ID可为空现场挂号无预约
*/
private Long appointmentId;
/**
* 顾客ID
*/
private Long customerId;
/**
* 宠物ID
*/
private Long petId;
/**
* 接诊医生ID
*/
private Long doctorId;
/**
* 就诊状态IN_PROGRESS-就诊中, COMPLETED-已完成
*/
private String status;
/**
* 总费用
*/
private BigDecimal totalAmount;
/**
* 支付状态UNPAID-未支付, PAID-已支付, REFUNDING-退款中, REFUNDED-已退款
*/
private String paymentStatus;
/**
* 支付方式OFFLINE-线下支付, ALIPAY-支付宝, WECHAT-微信支付
*/
private String paymentMethod;
/**
* 支付时间
*/
private LocalDateTime paymentTime;
/**
* 开始就诊时间
*/
private LocalDateTime startTime;
/**
* 结束就诊时间
*/
private LocalDateTime endTime;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
/**
* 删除标记0-未删除, 1-已删除
*/
@TableLogic
private Integer deleted;
}

View File

@@ -0,0 +1,9 @@
package com.gpf.pethospital.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.gpf.pethospital.entity.Appointment;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface AppointmentMapper extends BaseMapper<Appointment> {
}

View File

@@ -0,0 +1,9 @@
package com.gpf.pethospital.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.gpf.pethospital.entity.Drug;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface DrugMapper extends BaseMapper<Drug> {
}

View File

@@ -0,0 +1,9 @@
package com.gpf.pethospital.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.gpf.pethospital.entity.MedicalRecord;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface MedicalRecordMapper extends BaseMapper<MedicalRecord> {
}

View File

@@ -0,0 +1,9 @@
package com.gpf.pethospital.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.gpf.pethospital.entity.Message;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface MessageMapper extends BaseMapper<Message> {
}

View File

@@ -0,0 +1,9 @@
package com.gpf.pethospital.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.gpf.pethospital.entity.Notice;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface NoticeMapper extends BaseMapper<Notice> {
}

View File

@@ -0,0 +1,9 @@
package com.gpf.pethospital.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.gpf.pethospital.entity.Order;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface OrderMapper extends BaseMapper<Order> {
}

View File

@@ -0,0 +1,9 @@
package com.gpf.pethospital.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.gpf.pethospital.entity.Pet;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface PetMapper extends BaseMapper<Pet> {
}

View File

@@ -0,0 +1,9 @@
package com.gpf.pethospital.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.gpf.pethospital.entity.PrescriptionItem;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface PrescriptionItemMapper extends BaseMapper<PrescriptionItem> {
}

View File

@@ -0,0 +1,9 @@
package com.gpf.pethospital.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.gpf.pethospital.entity.Prescription;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface PrescriptionMapper extends BaseMapper<Prescription> {
}

View File

@@ -0,0 +1,9 @@
package com.gpf.pethospital.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.gpf.pethospital.entity.Report;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface ReportMapper extends BaseMapper<Report> {
}

View File

@@ -0,0 +1,9 @@
package com.gpf.pethospital.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.gpf.pethospital.entity.StockIn;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface StockInMapper extends BaseMapper<StockIn> {
}

View File

@@ -0,0 +1,9 @@
package com.gpf.pethospital.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.gpf.pethospital.entity.StockOut;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface StockOutMapper extends BaseMapper<StockOut> {
}

View File

@@ -0,0 +1,9 @@
package com.gpf.pethospital.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.gpf.pethospital.entity.User;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UserMapper extends BaseMapper<User> {
}

View File

@@ -0,0 +1,9 @@
package com.gpf.pethospital.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.gpf.pethospital.entity.VaccineRecord;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface VaccineRecordMapper extends BaseMapper<VaccineRecord> {
}

View File

@@ -0,0 +1,9 @@
package com.gpf.pethospital.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.gpf.pethospital.entity.Visit;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface VisitMapper extends BaseMapper<Visit> {
}

View File

@@ -0,0 +1,25 @@
package com.gpf.pethospital.security;
public class AuthUser {
private final Long id;
private final String username;
private final String role;
public AuthUser(Long id, String username, String role) {
this.id = id;
this.username = username;
this.role = role;
}
public Long getId() {
return id;
}
public String getUsername() {
return username;
}
public String getRole() {
return role;
}
}

View File

@@ -0,0 +1,55 @@
package com.gpf.pethospital.security;
import com.gpf.pethospital.util.JwtUtil;
import io.jsonwebtoken.Claims;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Collections;
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtUtil jwtUtil;
public JwtAuthenticationFilter(JwtUtil jwtUtil) {
this.jwtUtil = jwtUtil;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String token = resolveToken(request);
if (StringUtils.hasText(token)) {
try {
Claims claims = jwtUtil.parseToken(token);
String username = claims.get("username", String.class);
String role = claims.get("role", String.class);
Number uid = claims.get("uid", Number.class);
Long userId = uid == null ? null : uid.longValue();
SimpleGrantedAuthority authority = new SimpleGrantedAuthority("ROLE_" + role);
AuthUser principal = new AuthUser(userId, username, role);
Authentication auth = new UsernamePasswordAuthenticationToken(principal, null, Collections.singleton(authority));
SecurityContextHolder.getContext().setAuthentication(auth);
} catch (Exception ignored) {
}
}
filterChain.doFilter(request, response);
}
private String resolveToken(HttpServletRequest request) {
String bearer = request.getHeader("Authorization");
if (StringUtils.hasText(bearer) && bearer.startsWith("Bearer ")) {
return bearer.substring(7);
}
return null;
}
}

View File

@@ -0,0 +1,7 @@
package com.gpf.pethospital.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.gpf.pethospital.entity.Appointment;
public interface AppointmentService extends IService<Appointment> {
}

View File

@@ -0,0 +1,7 @@
package com.gpf.pethospital.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.gpf.pethospital.entity.Drug;
public interface DrugService extends IService<Drug> {
}

View File

@@ -0,0 +1,7 @@
package com.gpf.pethospital.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.gpf.pethospital.entity.MedicalRecord;
public interface MedicalRecordService extends IService<MedicalRecord> {
}

View File

@@ -0,0 +1,7 @@
package com.gpf.pethospital.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.gpf.pethospital.entity.Message;
public interface MessageService extends IService<Message> {
}

View File

@@ -0,0 +1,7 @@
package com.gpf.pethospital.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.gpf.pethospital.entity.Notice;
public interface NoticeService extends IService<Notice> {
}

View File

@@ -0,0 +1,7 @@
package com.gpf.pethospital.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.gpf.pethospital.entity.Order;
public interface OrderService extends IService<Order> {
}

View File

@@ -0,0 +1,7 @@
package com.gpf.pethospital.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.gpf.pethospital.entity.Pet;
public interface PetService extends IService<Pet> {
}

View File

@@ -0,0 +1,7 @@
package com.gpf.pethospital.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.gpf.pethospital.entity.PrescriptionItem;
public interface PrescriptionItemService extends IService<PrescriptionItem> {
}

View File

@@ -0,0 +1,7 @@
package com.gpf.pethospital.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.gpf.pethospital.entity.Prescription;
public interface PrescriptionService extends IService<Prescription> {
}

View File

@@ -0,0 +1,7 @@
package com.gpf.pethospital.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.gpf.pethospital.entity.Report;
public interface ReportService extends IService<Report> {
}

View File

@@ -0,0 +1,7 @@
package com.gpf.pethospital.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.gpf.pethospital.entity.StockIn;
public interface StockInService extends IService<StockIn> {
}

View File

@@ -0,0 +1,7 @@
package com.gpf.pethospital.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.gpf.pethospital.entity.StockOut;
public interface StockOutService extends IService<StockOut> {
}

View File

@@ -0,0 +1,7 @@
package com.gpf.pethospital.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.gpf.pethospital.entity.User;
public interface UserService extends IService<User> {
}

View File

@@ -0,0 +1,7 @@
package com.gpf.pethospital.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.gpf.pethospital.entity.VaccineRecord;
public interface VaccineRecordService extends IService<VaccineRecord> {
}

View File

@@ -0,0 +1,7 @@
package com.gpf.pethospital.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.gpf.pethospital.entity.Visit;
public interface VisitService extends IService<Visit> {
}

View File

@@ -0,0 +1,11 @@
package com.gpf.pethospital.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.gpf.pethospital.entity.Appointment;
import com.gpf.pethospital.mapper.AppointmentMapper;
import com.gpf.pethospital.service.AppointmentService;
import org.springframework.stereotype.Service;
@Service
public class AppointmentServiceImpl extends ServiceImpl<AppointmentMapper, Appointment> implements AppointmentService {
}

View File

@@ -0,0 +1,11 @@
package com.gpf.pethospital.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.gpf.pethospital.entity.Drug;
import com.gpf.pethospital.mapper.DrugMapper;
import com.gpf.pethospital.service.DrugService;
import org.springframework.stereotype.Service;
@Service
public class DrugServiceImpl extends ServiceImpl<DrugMapper, Drug> implements DrugService {
}

View File

@@ -0,0 +1,11 @@
package com.gpf.pethospital.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.gpf.pethospital.entity.MedicalRecord;
import com.gpf.pethospital.mapper.MedicalRecordMapper;
import com.gpf.pethospital.service.MedicalRecordService;
import org.springframework.stereotype.Service;
@Service
public class MedicalRecordServiceImpl extends ServiceImpl<MedicalRecordMapper, MedicalRecord> implements MedicalRecordService {
}

View File

@@ -0,0 +1,11 @@
package com.gpf.pethospital.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.gpf.pethospital.entity.Message;
import com.gpf.pethospital.mapper.MessageMapper;
import com.gpf.pethospital.service.MessageService;
import org.springframework.stereotype.Service;
@Service
public class MessageServiceImpl extends ServiceImpl<MessageMapper, Message> implements MessageService {
}

View File

@@ -0,0 +1,11 @@
package com.gpf.pethospital.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.gpf.pethospital.entity.Notice;
import com.gpf.pethospital.mapper.NoticeMapper;
import com.gpf.pethospital.service.NoticeService;
import org.springframework.stereotype.Service;
@Service
public class NoticeServiceImpl extends ServiceImpl<NoticeMapper, Notice> implements NoticeService {
}

View File

@@ -0,0 +1,11 @@
package com.gpf.pethospital.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.gpf.pethospital.entity.Order;
import com.gpf.pethospital.mapper.OrderMapper;
import com.gpf.pethospital.service.OrderService;
import org.springframework.stereotype.Service;
@Service
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements OrderService {
}

View File

@@ -0,0 +1,11 @@
package com.gpf.pethospital.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.gpf.pethospital.entity.Pet;
import com.gpf.pethospital.mapper.PetMapper;
import com.gpf.pethospital.service.PetService;
import org.springframework.stereotype.Service;
@Service
public class PetServiceImpl extends ServiceImpl<PetMapper, Pet> implements PetService {
}

View File

@@ -0,0 +1,11 @@
package com.gpf.pethospital.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.gpf.pethospital.entity.PrescriptionItem;
import com.gpf.pethospital.mapper.PrescriptionItemMapper;
import com.gpf.pethospital.service.PrescriptionItemService;
import org.springframework.stereotype.Service;
@Service
public class PrescriptionItemServiceImpl extends ServiceImpl<PrescriptionItemMapper, PrescriptionItem> implements PrescriptionItemService {
}

View File

@@ -0,0 +1,11 @@
package com.gpf.pethospital.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.gpf.pethospital.entity.Prescription;
import com.gpf.pethospital.mapper.PrescriptionMapper;
import com.gpf.pethospital.service.PrescriptionService;
import org.springframework.stereotype.Service;
@Service
public class PrescriptionServiceImpl extends ServiceImpl<PrescriptionMapper, Prescription> implements PrescriptionService {
}

View File

@@ -0,0 +1,11 @@
package com.gpf.pethospital.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.gpf.pethospital.entity.Report;
import com.gpf.pethospital.mapper.ReportMapper;
import com.gpf.pethospital.service.ReportService;
import org.springframework.stereotype.Service;
@Service
public class ReportServiceImpl extends ServiceImpl<ReportMapper, Report> implements ReportService {
}

View File

@@ -0,0 +1,11 @@
package com.gpf.pethospital.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.gpf.pethospital.entity.StockIn;
import com.gpf.pethospital.mapper.StockInMapper;
import com.gpf.pethospital.service.StockInService;
import org.springframework.stereotype.Service;
@Service
public class StockInServiceImpl extends ServiceImpl<StockInMapper, StockIn> implements StockInService {
}

View File

@@ -0,0 +1,11 @@
package com.gpf.pethospital.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.gpf.pethospital.entity.StockOut;
import com.gpf.pethospital.mapper.StockOutMapper;
import com.gpf.pethospital.service.StockOutService;
import org.springframework.stereotype.Service;
@Service
public class StockOutServiceImpl extends ServiceImpl<StockOutMapper, StockOut> implements StockOutService {
}

View File

@@ -0,0 +1,11 @@
package com.gpf.pethospital.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.gpf.pethospital.entity.User;
import com.gpf.pethospital.mapper.UserMapper;
import com.gpf.pethospital.service.UserService;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}

View File

@@ -0,0 +1,11 @@
package com.gpf.pethospital.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.gpf.pethospital.entity.VaccineRecord;
import com.gpf.pethospital.mapper.VaccineRecordMapper;
import com.gpf.pethospital.service.VaccineRecordService;
import org.springframework.stereotype.Service;
@Service
public class VaccineRecordServiceImpl extends ServiceImpl<VaccineRecordMapper, VaccineRecord> implements VaccineRecordService {
}

View File

@@ -0,0 +1,11 @@
package com.gpf.pethospital.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.gpf.pethospital.entity.Visit;
import com.gpf.pethospital.mapper.VisitMapper;
import com.gpf.pethospital.service.VisitService;
import org.springframework.stereotype.Service;
@Service
public class VisitServiceImpl extends ServiceImpl<VisitMapper, Visit> implements VisitService {
}

View File

@@ -0,0 +1,41 @@
package com.gpf.pethospital.util;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
public class JwtUtil {
private final String secret;
private final long expirationMs;
public JwtUtil(String secret, long expirationMs) {
this.secret = secret;
this.expirationMs = expirationMs;
}
public String generateToken(Long userId, String role, String username) {
Map<String, Object> claims = new HashMap<>();
claims.put("uid", userId);
claims.put("role", role);
claims.put("username", username);
Date now = new Date();
Date exp = new Date(now.getTime() + expirationMs);
return Jwts.builder()
.setClaims(claims)
.setIssuedAt(now)
.setExpiration(exp)
.signWith(SignatureAlgorithm.HS256, secret)
.compact();
}
public Claims parseToken(String token) {
return Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
}
}

View File

@@ -0,0 +1,15 @@
package com.gpf.pethospital.util;
import com.gpf.pethospital.security.AuthUser;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
public class SecurityUtils {
public static AuthUser currentUser() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && authentication.getPrincipal() instanceof AuthUser) {
return (AuthUser) authentication.getPrincipal();
}
return null;
}
}

View File

@@ -0,0 +1,61 @@
server:
port: 8081
servlet:
context-path: /api
spring:
application:
name: pet-hospital
datasource:
driver-class-name: org.h2.Driver
url: jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE;MODE=MYSQL;
username: sa
password: password
hikari:
maximum-pool-size: 10
minimum-idle: 5
connection-timeout: 30000
jackson:
time-zone: GMT+8
date-format: yyyy-MM-dd HH:mm:ss
h2:
console:
enabled: true
path: /h2-console
jpa:
hibernate:
ddl-auto: create-drop
show-sql: true
sql:
init:
mode: always
schema-locations: classpath*:schema-h2.sql
data-locations: classpath*:data.sql
mybatis-plus:
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
id-type: auto
logic-delete-field: deleted
logic-delete-value: 1
logic-not-delete-value: 0
mapper-locations: classpath*:mapper/**/*.xml
ddl-auto: create-drop
# JWT配置
jwt:
secret: petHospitalSecretKey2024GuanPengFeiGraduateDesign
expiration: 86400000 # 24小时
# 文件上传配置
file:
upload-path: /tmp/pet-hospital/uploads/
max-size: 10MB

View File

@@ -0,0 +1,36 @@
server:
port: 8080
servlet:
context-path: /api
spring:
profiles:
active: dev
application:
name: pet-hospital
jackson:
time-zone: GMT+8
date-format: yyyy-MM-dd HH:mm:ss
mybatis-plus:
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
id-type: auto
logic-delete-field: deleted
logic-delete-value: 1
logic-not-delete-value: 0
mapper-locations: classpath*:mapper/**/*.xml
# JWT配置
jwt:
secret: pet-hospital-secret-key-2024-guanpengfei-graduate-design
expiration: 86400000 # 24小时
# 文件上传配置
file:
upload-path: /tmp/pet-hospital/uploads/
max-size: 10MB

View File

@@ -0,0 +1,17 @@
-- 插入初始管理员用户
INSERT INTO `user` (username, password, phone, email, role, status) VALUES ('admin', '$2a$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', '13800138000', 'admin@example.com', 'ADMIN', 1);
-- 插入初始医生用户
INSERT INTO `user` (username, password, phone, email, role, status) VALUES ('doctor', '$2a$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', '13700137000', 'doctor@example.com', 'DOCTOR', 1);
-- 插入初始顾客用户
INSERT INTO `user` (username, password, phone, email, role, status) VALUES ('customer', '$2a$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', '13900139000', 'customer@example.com', 'CUSTOMER', 1);
-- 插入示例宠物
INSERT INTO pet (name, species, breed, gender, birthday, owner_id) VALUES ('小白', '', '金毛', 'MALE', '2023-01-01', 3);
-- 插入示例医生
INSERT INTO doctor (name, department, title, phone, email, status) VALUES ('张医生', '内科', '主治医师', '13600136000', 'zhang@hospital.com', 1);
-- 插入示例药品
INSERT INTO drug (name, category, manufacturer, specification, unit_price, stock_quantity, unit, status) VALUES ('阿莫西林', '抗生素', '制药厂A', '0.25g*24粒', 25.50, 100, '', 1);

View File

@@ -0,0 +1,242 @@
-- 如果表不存在则创建表,如果存在则添加缺失的列
-- 检查并创建appointment表或添加缺失列
CREATE TABLE IF NOT EXISTS appointment (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
customer_id BIGINT NOT NULL,
pet_id BIGINT NOT NULL,
doctor_id BIGINT,
department VARCHAR(50),
appointment_date DATE,
time_slot VARCHAR(20), -- 添加这个缺失的列
status VARCHAR(20) DEFAULT 'PENDING',
remark TEXT,
cancel_time TIMESTAMP NULL,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted INT DEFAULT 0
);
-- 检查并创建visit表
CREATE TABLE IF NOT EXISTS visit (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
appointment_id BIGINT,
customer_id BIGINT NOT NULL,
pet_id BIGINT NOT NULL,
doctor_id BIGINT NOT NULL,
symptoms TEXT,
diagnosis TEXT,
treatment_plan TEXT,
status VARCHAR(20) DEFAULT 'PENDING',
total_amount DECIMAL(10,2),
payment_status VARCHAR(20) DEFAULT 'UNPAID',
payment_method VARCHAR(20),
payment_time TIMESTAMP NULL,
start_time TIMESTAMP,
end_time TIMESTAMP,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted INT DEFAULT 0
);
-- 检查并创建prescription表
CREATE TABLE IF NOT EXISTS prescription (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
visit_id BIGINT,
doctor_id BIGINT,
customer_id BIGINT,
total_amount DECIMAL(10,2),
status VARCHAR(20) DEFAULT 'DRAFT',
remark TEXT,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted INT DEFAULT 0
);
-- 检查并创建prescription_item表
CREATE TABLE IF NOT EXISTS prescription_item (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
prescription_id BIGINT NOT NULL,
drug_id BIGINT NOT NULL,
quantity INT NOT NULL,
dosage VARCHAR(100),
frequency VARCHAR(50),
duration VARCHAR(50),
usage_instructions TEXT,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted INT DEFAULT 0
);
-- 检查并创建order_info表
CREATE TABLE IF NOT EXISTS order_info (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
visit_id BIGINT,
customer_id BIGINT NOT NULL,
amount DECIMAL(10,2),
status VARCHAR(20) DEFAULT 'UNPAID',
payment_method VARCHAR(20),
payment_time TIMESTAMP NULL,
remark TEXT,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted INT DEFAULT 0
);
-- 检查并创建drug表
CREATE TABLE IF NOT EXISTS drug (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
category VARCHAR(50),
manufacturer VARCHAR(100),
specification VARCHAR(100),
unit_price DECIMAL(10,2),
stock_quantity INT DEFAULT 0,
unit VARCHAR(20),
status INT DEFAULT 1,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted INT DEFAULT 0
);
-- 检查并创建pet表
CREATE TABLE IF NOT EXISTS pet (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
owner_id BIGINT NOT NULL,
name VARCHAR(50) NOT NULL,
species VARCHAR(50),
breed VARCHAR(100),
gender VARCHAR(10), -- 修改为VARCHAR以支持MALE/FEMALE
birthday DATE, -- 添加birthday字段而不是age
weight DOUBLE, -- 添加weight字段
photo VARCHAR(255), -- 添加photo字段
remark TEXT, -- 添加remark字段
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted INT DEFAULT 0
);
-- 检查并创建doctor表
CREATE TABLE IF NOT EXISTS doctor (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50) NOT NULL,
department VARCHAR(50),
title VARCHAR(50),
phone VARCHAR(20),
email VARCHAR(100),
avatar VARCHAR(255),
status INT DEFAULT 1,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted INT DEFAULT 0
);
-- 检查并创建user表
CREATE TABLE IF NOT EXISTS `user` (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
phone VARCHAR(20),
email VARCHAR(100),
password VARCHAR(255) NOT NULL,
role VARCHAR(20) DEFAULT 'CUSTOMER',
status INT DEFAULT 1,
avatar VARCHAR(255),
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted INT DEFAULT 0
);
-- 检查并创建medical_record表
CREATE TABLE IF NOT EXISTS medical_record (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
visit_id BIGINT NOT NULL,
record_type VARCHAR(50), -- CHECKUP体检, DIAGNOSIS诊断, TREATMENT治疗
content TEXT,
attachment_urls TEXT,
doctor_id BIGINT,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted INT DEFAULT 0
);
-- 检查并创建message表
CREATE TABLE IF NOT EXISTS message (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
sender_id BIGINT,
receiver_id BIGINT NOT NULL,
content TEXT NOT NULL,
type VARCHAR(20) DEFAULT 'NOTICE', -- NOTICE通知, CHAT聊天
status VARCHAR(20) DEFAULT 'UNREAD', -- UNREAD未读, READ已读
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted INT DEFAULT 0
);
-- 检查并创建notice表
CREATE TABLE IF NOT EXISTS notice (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(200) NOT NULL,
content TEXT NOT NULL,
publisher_id BIGINT NOT NULL,
publish_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
status INT DEFAULT 1,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted INT DEFAULT 0
);
-- 检查并创建report表
CREATE TABLE IF NOT EXISTS report (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
report_type VARCHAR(50) NOT NULL, -- REVENUE收入, CUSTOMER客户, PET宠物, DRUG药品
report_data JSON,
period_start DATE,
period_end DATE,
generated_by BIGINT,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted INT DEFAULT 0
);
-- 检查并创建stock_in表
CREATE TABLE IF NOT EXISTS stock_in (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
drug_id BIGINT NOT NULL,
quantity INT NOT NULL,
unit_price DECIMAL(10,2),
supplier VARCHAR(100),
operator_id BIGINT,
remark TEXT,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted INT DEFAULT 0
);
-- 检查并创建stock_out表
CREATE TABLE IF NOT EXISTS stock_out (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
drug_id BIGINT NOT NULL,
quantity INT NOT NULL,
unit_price DECIMAL(10,2),
purpose VARCHAR(100), -- 用途:销售、损耗等
operator_id BIGINT,
remark TEXT,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted INT DEFAULT 0
);
-- 检查并创建vaccine_record表
CREATE TABLE IF NOT EXISTS vaccine_record (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
pet_id BIGINT NOT NULL,
vaccine_name VARCHAR(100) NOT NULL,
dose_number INT,
injection_date DATE,
next_appointment_date DATE,
doctor_id BIGINT,
remark TEXT,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted INT DEFAULT 0
);

View File

@@ -0,0 +1,230 @@
-- 用户表
CREATE TABLE IF NOT EXISTS `user` (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
phone VARCHAR(20),
email VARCHAR(100),
password VARCHAR(255) NOT NULL,
role VARCHAR(20) DEFAULT 'CUSTOMER',
status INT DEFAULT 1,
avatar VARCHAR(255),
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted INT DEFAULT 0
);
-- 宠物表
CREATE TABLE IF NOT EXISTS pet (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50) NOT NULL,
species VARCHAR(50),
breed VARCHAR(100),
gender CHAR(1),
age INT,
owner_id BIGINT NOT NULL,
health_status VARCHAR(100),
vaccination_status VARCHAR(100),
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted INT DEFAULT 0
);
-- 医生表
CREATE TABLE IF NOT EXISTS doctor (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50) NOT NULL,
department VARCHAR(50),
title VARCHAR(50),
phone VARCHAR(20),
email VARCHAR(100),
avatar VARCHAR(255),
status INT DEFAULT 1,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted INT DEFAULT 0
);
-- 就诊记录表
CREATE TABLE IF NOT EXISTS visit (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
pet_id BIGINT NOT NULL,
doctor_id BIGINT NOT NULL,
customer_id BIGINT NOT NULL,
symptoms TEXT,
diagnosis TEXT,
treatment_plan TEXT,
visit_date DATE,
status VARCHAR(20) DEFAULT 'PENDING',
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted INT DEFAULT 0
);
-- 处方表
CREATE TABLE IF NOT EXISTS prescription (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
visit_id BIGINT NOT NULL,
doctor_id BIGINT NOT NULL,
customer_id BIGINT NOT NULL,
total_amount DECIMAL(10,2),
status VARCHAR(20) DEFAULT 'ACTIVE',
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted INT DEFAULT 0
);
-- 处方明细表
CREATE TABLE IF NOT EXISTS prescription_item (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
prescription_id BIGINT NOT NULL,
drug_id BIGINT NOT NULL,
quantity INT NOT NULL,
dosage VARCHAR(100),
frequency VARCHAR(50),
duration VARCHAR(50),
usage_instructions TEXT,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted INT DEFAULT 0
);
-- 药品表
CREATE TABLE IF NOT EXISTS drug (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
category VARCHAR(50),
manufacturer VARCHAR(100),
specification VARCHAR(100),
unit_price DECIMAL(10,2),
stock_quantity INT DEFAULT 0,
unit VARCHAR(20),
status INT DEFAULT 1,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted INT DEFAULT 0
);
-- 订单表
CREATE TABLE IF NOT EXISTS order_info (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
visit_id BIGINT,
customer_id BIGINT NOT NULL,
amount DECIMAL(10,2),
status VARCHAR(20) DEFAULT 'UNPAID',
payment_method VARCHAR(20),
payment_time TIMESTAMP NULL,
remark TEXT,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted INT DEFAULT 0
);
-- 预约表
CREATE TABLE IF NOT EXISTS appointment (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
pet_id BIGINT NOT NULL,
doctor_id BIGINT NOT NULL,
customer_id BIGINT NOT NULL,
appointment_date DATE NOT NULL,
appointment_time TIME NOT NULL,
reason TEXT,
status VARCHAR(20) DEFAULT 'PENDING',
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted INT DEFAULT 0
);
-- 疫苗接种记录表
CREATE TABLE IF NOT EXISTS vaccine_record (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
pet_id BIGINT NOT NULL,
vaccine_name VARCHAR(100) NOT NULL,
dose_number INT,
injection_date DATE,
next_appointment_date DATE,
doctor_id BIGINT,
remark TEXT,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted INT DEFAULT 0
);
-- 库存入库表
CREATE TABLE IF NOT EXISTS stock_in (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
drug_id BIGINT NOT NULL,
quantity INT NOT NULL,
unit_price DECIMAL(10,2),
supplier VARCHAR(100),
operator_id BIGINT,
remark TEXT,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted INT DEFAULT 0
);
-- 库存出库表
CREATE TABLE IF NOT EXISTS stock_out (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
drug_id BIGINT NOT NULL,
quantity INT NOT NULL,
unit_price DECIMAL(10,2),
purpose VARCHAR(100), -- 用途:销售、损耗等
operator_id BIGINT,
remark TEXT,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted INT DEFAULT 0
);
-- 消息表
CREATE TABLE IF NOT EXISTS message (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
sender_id BIGINT,
receiver_id BIGINT NOT NULL,
content TEXT NOT NULL,
type VARCHAR(20) DEFAULT 'NOTICE', -- NOTICE通知, CHAT聊天
status VARCHAR(20) DEFAULT 'UNREAD', -- UNREAD未读, READ已读
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted INT DEFAULT 0
);
-- 公告表
CREATE TABLE IF NOT EXISTS notice (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(200) NOT NULL,
content TEXT NOT NULL,
publisher_id BIGINT NOT NULL,
publish_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
status INT DEFAULT 1,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted INT DEFAULT 0
);
-- 医疗记录表
CREATE TABLE IF NOT EXISTS medical_record (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
visit_id BIGINT NOT NULL,
record_type VARCHAR(50), -- CHECKUP体检, DIAGNOSIS诊断, TREATMENT治疗
content TEXT,
attachment_urls TEXT,
doctor_id BIGINT,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted INT DEFAULT 0
);
-- 报表统计表
CREATE TABLE IF NOT EXISTS report (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
report_type VARCHAR(50) NOT NULL, -- REVENUE收入, CUSTOMER客户, PET宠物, DRUG药品
report_data JSON,
period_start DATE,
period_end DATE,
generated_by BIGINT,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted INT DEFAULT 0
);