Compare commits
No commits in common. "e230317fe9b42f756d4abdb0b33bc54bb42fbb57" and "3c04019af4f29deff170ec5c77a74c88e8f90692" have entirely different histories.
e230317fe9
...
3c04019af4
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
target/
|
||||||
|
uploads/
|
||||||
201
LICENSE
Normal file
201
LICENSE
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
36
README.en.md
Normal file
36
README.en.md
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
# petstore-backend
|
||||||
|
|
||||||
|
#### Description
|
||||||
|
宠伴生活馆
|
||||||
|
|
||||||
|
#### Software Architecture
|
||||||
|
Software architecture description
|
||||||
|
|
||||||
|
#### Installation
|
||||||
|
|
||||||
|
1. xxxx
|
||||||
|
2. xxxx
|
||||||
|
3. xxxx
|
||||||
|
|
||||||
|
#### Instructions
|
||||||
|
|
||||||
|
1. xxxx
|
||||||
|
2. xxxx
|
||||||
|
3. xxxx
|
||||||
|
|
||||||
|
#### Contribution
|
||||||
|
|
||||||
|
1. Fork the repository
|
||||||
|
2. Create Feat_xxx branch
|
||||||
|
3. Commit your code
|
||||||
|
4. Create Pull Request
|
||||||
|
|
||||||
|
|
||||||
|
#### Gitee Feature
|
||||||
|
|
||||||
|
1. You can use Readme\_XXX.md to support different languages, such as Readme\_en.md, Readme\_zh.md
|
||||||
|
2. Gitee blog [blog.gitee.com](https://blog.gitee.com)
|
||||||
|
3. Explore open source project [https://gitee.com/explore](https://gitee.com/explore)
|
||||||
|
4. The most valuable open source project [GVP](https://gitee.com/gvp)
|
||||||
|
5. The manual of Gitee [https://gitee.com/help](https://gitee.com/help)
|
||||||
|
6. The most popular members [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)
|
||||||
35
README.md
35
README.md
@ -1,2 +1,37 @@
|
|||||||
# petstore-backend
|
# petstore-backend
|
||||||
|
|
||||||
|
#### 介绍
|
||||||
|
宠伴生活馆
|
||||||
|
|
||||||
|
#### 软件架构
|
||||||
|
软件架构说明
|
||||||
|
|
||||||
|
|
||||||
|
#### 安装教程
|
||||||
|
|
||||||
|
1. xxxx
|
||||||
|
2. xxxx
|
||||||
|
3. xxxx
|
||||||
|
|
||||||
|
#### 使用说明
|
||||||
|
|
||||||
|
1. xxxx
|
||||||
|
2. xxxx
|
||||||
|
3. xxxx
|
||||||
|
|
||||||
|
#### 参与贡献
|
||||||
|
|
||||||
|
1. Fork 本仓库
|
||||||
|
2. 新建 Feat_xxx 分支
|
||||||
|
3. 提交代码
|
||||||
|
4. 新建 Pull Request
|
||||||
|
|
||||||
|
|
||||||
|
#### 特技
|
||||||
|
|
||||||
|
1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md
|
||||||
|
2. Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com)
|
||||||
|
3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目
|
||||||
|
4. [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目
|
||||||
|
5. Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help)
|
||||||
|
6. Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)
|
||||||
|
|||||||
96
pom.xml
Normal file
96
pom.xml
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-parent</artifactId>
|
||||||
|
<version>3.2.3</version>
|
||||||
|
<relativePath/>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<groupId>com.petstore</groupId>
|
||||||
|
<artifactId>petstore-backend</artifactId>
|
||||||
|
<version>1.0.0</version>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
<name>petstore-backend</name>
|
||||||
|
<description>宠伴生活馆 后端服务</description>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<java.version>17</java.version>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.mysql</groupId>
|
||||||
|
<artifactId>mysql-connector-j</artifactId>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<version>1.18.44</version>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-validation</artifactId>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<version>3.13.0</version>
|
||||||
|
<configuration>
|
||||||
|
<source>17</source>
|
||||||
|
<target>17</target>
|
||||||
|
<fork>true</fork>
|
||||||
|
<compilerArgs>
|
||||||
|
<arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED</arg>
|
||||||
|
<arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED</arg>
|
||||||
|
<arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED</arg>
|
||||||
|
<arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED</arg>
|
||||||
|
<arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED</arg>
|
||||||
|
<arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED</arg>
|
||||||
|
<arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED</arg>
|
||||||
|
<arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED</arg>
|
||||||
|
<arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED</arg>
|
||||||
|
<arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.jvm=ALL-UNNAMED</arg>
|
||||||
|
</compilerArgs>
|
||||||
|
<annotationProcessorPaths>
|
||||||
|
<path>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<version>1.18.44</version>
|
||||||
|
</path>
|
||||||
|
</annotationProcessorPaths>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<excludes>
|
||||||
|
<exclude>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
</exclude>
|
||||||
|
</excludes>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
||||||
30
src/main/java/com/petstore/PetstoreApplication.java
Normal file
30
src/main/java/com/petstore/PetstoreApplication.java
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
package com.petstore;
|
||||||
|
|
||||||
|
import com.petstore.service.ServiceTypeService;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
import org.springframework.boot.CommandLineRunner;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.jdbc.core.JdbcTemplate;
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class PetstoreApplication {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
System.out.println(">>> working dir: " + System.getProperty("user.dir"));
|
||||||
|
SpringApplication.run(PetstoreApplication.class, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
CommandLineRunner initRunner(ServiceTypeService serviceTypeService, JdbcTemplate jdbc) {
|
||||||
|
return args -> {
|
||||||
|
serviceTypeService.initDefaults();
|
||||||
|
// appointment_id 改为允许 NULL(支持不挂预约直接填报告)
|
||||||
|
jdbc.execute("ALTER TABLE t_report MODIFY COLUMN appointment_id BIGINT NULL");
|
||||||
|
// 修复旧图片URL:/2026/xxx → /api/upload/image/2026/xxx
|
||||||
|
jdbc.execute("UPDATE t_report SET before_photo = REPLACE(before_photo, 'http://localhost:8080/2026/', '/api/upload/image/2026/') WHERE before_photo LIKE 'http://localhost:8080/2026/%'");
|
||||||
|
jdbc.execute("UPDATE t_report SET after_photo = REPLACE(after_photo, 'http://localhost:8080/2026/', '/api/upload/image/2026/') WHERE after_photo LIKE 'http://localhost:8080/2026/%'");
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
23
src/main/java/com/petstore/config/CorsConfig.java
Normal file
23
src/main/java/com/petstore/config/CorsConfig.java
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package com.petstore.config;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.web.cors.CorsConfiguration;
|
||||||
|
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||||
|
import org.springframework.web.filter.CorsFilter;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class CorsConfig {
|
||||||
|
@Bean
|
||||||
|
public CorsFilter corsFilter() {
|
||||||
|
CorsConfiguration config = new CorsConfiguration();
|
||||||
|
config.setAllowCredentials(true);
|
||||||
|
config.addAllowedOriginPattern("*");
|
||||||
|
config.addAllowedHeader("*");
|
||||||
|
config.addAllowedMethod("*");
|
||||||
|
|
||||||
|
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||||
|
source.registerCorsConfiguration("/**", config);
|
||||||
|
return new CorsFilter(source);
|
||||||
|
}
|
||||||
|
}
|
||||||
27
src/main/java/com/petstore/config/WebConfig.java
Normal file
27
src/main/java/com/petstore/config/WebConfig.java
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
package com.petstore.config;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
|
||||||
|
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class WebConfig implements WebMvcConfigurer {
|
||||||
|
|
||||||
|
@Value("${upload.path:uploads}")
|
||||||
|
private String uploadPath;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addResourceHandlers(ResourceHandlerRegistry registry) {
|
||||||
|
// 硬编码绝对路径,确保能找到文件
|
||||||
|
String uploadDir = "/Users/wac/Desktop/www/_src/petstore/backend/uploads/";
|
||||||
|
System.out.println(">>> WebConfig uploadDir: " + uploadDir);
|
||||||
|
System.out.println(">>> /2026 exists: " + new File(uploadDir + "2026/04/01/").exists());
|
||||||
|
registry.addResourceHandler("/uploads/**")
|
||||||
|
.addResourceLocations("file:" + uploadDir);
|
||||||
|
registry.addResourceHandler("/2026/**")
|
||||||
|
.addResourceLocations("file:" + uploadDir);
|
||||||
|
}
|
||||||
|
}
|
||||||
33
src/main/java/com/petstore/config/WechatConfig.java
Normal file
33
src/main/java/com/petstore/config/WechatConfig.java
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
package com.petstore.config;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class WechatConfig {
|
||||||
|
// TODO: 替换为实际的微信开放平台 AppID 和 AppSecret
|
||||||
|
@Value("${wechat.appid:YOUR_APPID}")
|
||||||
|
private String appid;
|
||||||
|
|
||||||
|
@Value("${wechat.appsecret:YOUR_APPSECRET}")
|
||||||
|
private String appsecret;
|
||||||
|
|
||||||
|
@Value("${wechat.redirect_uri:http://localhost:8080/api/wechat/callback}")
|
||||||
|
private String redirectUri;
|
||||||
|
|
||||||
|
public String getAppid() { return appid; }
|
||||||
|
public String getAppsecret() { return appsecret; }
|
||||||
|
public String getRedirectUri() { return redirectUri; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取微信授权跳转地址
|
||||||
|
*/
|
||||||
|
public String getAuthorizeUrl() {
|
||||||
|
return "https://open.weixin.qq.com/connect/qrconnect" +
|
||||||
|
"?appid=" + appid +
|
||||||
|
"&redirect_uri=" + redirectUri +
|
||||||
|
"&response_type=code" +
|
||||||
|
"&scope=snsapi_login" +
|
||||||
|
"&state=petstore#wechat_redirect";
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,86 @@
|
|||||||
|
package com.petstore.controller;
|
||||||
|
|
||||||
|
import com.petstore.entity.Appointment;
|
||||||
|
import com.petstore.service.AppointmentService;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/appointment")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@CrossOrigin
|
||||||
|
public class AppointmentController {
|
||||||
|
private final AppointmentService appointmentService;
|
||||||
|
|
||||||
|
/** 获取预约列表(员工查自己/老板查全店) */
|
||||||
|
@GetMapping("/list")
|
||||||
|
public Map<String, Object> list(
|
||||||
|
@RequestParam(required = false) Long userId,
|
||||||
|
@RequestParam(required = false) Long storeId,
|
||||||
|
@RequestParam(required = false) String status) {
|
||||||
|
|
||||||
|
List<Appointment> appointments;
|
||||||
|
if (storeId != null) {
|
||||||
|
appointments = (status != null && !status.isEmpty())
|
||||||
|
? appointmentService.getByStoreIdAndStatus(storeId, status)
|
||||||
|
: appointmentService.getByStoreId(storeId);
|
||||||
|
} else if (userId != null) {
|
||||||
|
appointments = (status != null && !status.isEmpty())
|
||||||
|
? appointmentService.getByUserIdAndStatus(userId, status)
|
||||||
|
: appointmentService.getByUserId(userId);
|
||||||
|
} else {
|
||||||
|
return Map.of("code", 400, "message", "userId或storeId必填");
|
||||||
|
}
|
||||||
|
|
||||||
|
return Map.of("code", 200, "data", appointments);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 创建预约 */
|
||||||
|
@PostMapping("/create")
|
||||||
|
public Map<String, Object> create(@RequestBody Map<String, Object> params) {
|
||||||
|
Appointment appointment = new Appointment();
|
||||||
|
appointment.setPetName(params.get("petName").toString());
|
||||||
|
appointment.setPetType(params.get("petType").toString());
|
||||||
|
appointment.setServiceType(params.get("serviceType").toString());
|
||||||
|
|
||||||
|
String timeStr = params.get("appointmentTime").toString();
|
||||||
|
appointment.setAppointmentTime(java.time.LocalDateTime.parse(timeStr));
|
||||||
|
|
||||||
|
appointment.setStoreId(Long.valueOf(params.get("storeId").toString()));
|
||||||
|
appointment.setUserId(Long.valueOf(params.get("userId").toString()));
|
||||||
|
appointment.setStatus("new");
|
||||||
|
|
||||||
|
if (params.containsKey("remark") && params.get("remark") != null) {
|
||||||
|
appointment.setRemark(params.get("remark").toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
Appointment created = appointmentService.create(appointment);
|
||||||
|
return Map.of("code", 200, "message", "创建成功", "data", created);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 开始服务:状态变进行中 + 指定技师 */
|
||||||
|
@PostMapping("/start")
|
||||||
|
public Map<String, Object> start(@RequestBody Map<String, Object> params) {
|
||||||
|
Long appointmentId = Long.valueOf(params.get("appointmentId").toString());
|
||||||
|
Long staffUserId = Long.valueOf(params.get("staffUserId").toString());
|
||||||
|
Appointment updated = appointmentService.startService(appointmentId, staffUserId);
|
||||||
|
if (updated != null) {
|
||||||
|
return Map.of("code", 200, "message", "已开始服务", "data", updated);
|
||||||
|
}
|
||||||
|
return Map.of("code", 404, "message", "预约不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 更新预约状态 */
|
||||||
|
@PutMapping("/status")
|
||||||
|
public Map<String, Object> updateStatus(@RequestParam Long id, @RequestParam String status) {
|
||||||
|
Appointment updated = appointmentService.updateStatus(id, status);
|
||||||
|
if (updated != null) {
|
||||||
|
return Map.of("code", 200, "message", "更新成功", "data", updated);
|
||||||
|
}
|
||||||
|
return Map.of("code", 404, "message", "预约不存在");
|
||||||
|
}
|
||||||
|
}
|
||||||
114
src/main/java/com/petstore/controller/FileController.java
Normal file
114
src/main/java/com/petstore/controller/FileController.java
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
package com.petstore.controller;
|
||||||
|
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.core.io.FileSystemResource;
|
||||||
|
import org.springframework.core.io.Resource;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/upload")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@CrossOrigin
|
||||||
|
public class FileController {
|
||||||
|
|
||||||
|
@Value("${upload.path:uploads/}")
|
||||||
|
private String uploadPath;
|
||||||
|
|
||||||
|
@GetMapping("/image/**")
|
||||||
|
public ResponseEntity<Resource> getImage(HttpServletRequest request) throws IOException {
|
||||||
|
String path = request.getRequestURI().replace("/api/upload/image", "");
|
||||||
|
String basePath = uploadPath.endsWith("/") ? uploadPath : uploadPath + "/";
|
||||||
|
File file = new File(basePath + path);
|
||||||
|
if (!file.exists()) {
|
||||||
|
return ResponseEntity.notFound().build();
|
||||||
|
}
|
||||||
|
String contentType = Files.probeContentType(file.toPath());
|
||||||
|
if (contentType == null) contentType = "image/jpeg";
|
||||||
|
return ResponseEntity.ok()
|
||||||
|
.contentType(MediaType.parseMediaType(contentType))
|
||||||
|
.body(new FileSystemResource(file));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 兼容旧路径:/2026/04/01/xxx.jpg
|
||||||
|
@GetMapping("/legacy/**")
|
||||||
|
public ResponseEntity<Resource> getLegacyImage(HttpServletRequest request) throws IOException {
|
||||||
|
String path = request.getRequestURI().replace("/api/upload/legacy", "");
|
||||||
|
String basePath = uploadPath.endsWith("/") ? uploadPath : uploadPath + "/";
|
||||||
|
File file = new File(basePath + path);
|
||||||
|
if (!file.exists()) {
|
||||||
|
return ResponseEntity.notFound().build();
|
||||||
|
}
|
||||||
|
String contentType = Files.probeContentType(file.toPath());
|
||||||
|
if (contentType == null) contentType = "image/jpeg";
|
||||||
|
return ResponseEntity.ok()
|
||||||
|
.contentType(MediaType.parseMediaType(contentType))
|
||||||
|
.body(new FileSystemResource(file));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/image")
|
||||||
|
public Map<String, Object> uploadImage(@RequestParam("file") MultipartFile file) {
|
||||||
|
Map<String, Object> result = new HashMap<>();
|
||||||
|
|
||||||
|
if (file.isEmpty()) {
|
||||||
|
result.put("code", 400);
|
||||||
|
result.put("message", "文件为空");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
String contentType = file.getContentType();
|
||||||
|
if (contentType == null || !contentType.startsWith("image/")) {
|
||||||
|
result.put("code", 400);
|
||||||
|
result.put("message", "只能上传图片");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 创建上传目录
|
||||||
|
String datePath = LocalDate.now().toString().replace("-", "/");
|
||||||
|
String dirPath = uploadPath.endsWith("/") ? uploadPath + datePath : uploadPath + "/" + datePath;
|
||||||
|
File dir = new File(dirPath);
|
||||||
|
if (!dir.exists()) dir.mkdirs();
|
||||||
|
|
||||||
|
// 生成文件名
|
||||||
|
String originalFilename = file.getOriginalFilename();
|
||||||
|
String ext = "";
|
||||||
|
if (originalFilename != null && originalFilename.contains(".")) {
|
||||||
|
ext = originalFilename.substring(originalFilename.lastIndexOf("."));
|
||||||
|
}
|
||||||
|
String filename = UUID.randomUUID().toString().replace("-", "") + ext;
|
||||||
|
|
||||||
|
// 保存文件
|
||||||
|
Path filePath = Paths.get(dirPath, filename);
|
||||||
|
Files.write(filePath, file.getBytes());
|
||||||
|
|
||||||
|
// 返回访问URL(/api/upload/image/ + 日期路径 + 文件名)
|
||||||
|
String url = "/api/upload/image/" + datePath + "/" + filename;
|
||||||
|
|
||||||
|
result.put("code", 200);
|
||||||
|
result.put("message", "上传成功");
|
||||||
|
result.put("data", Map.of("url", url));
|
||||||
|
return result;
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
result.put("code", 500);
|
||||||
|
result.put("message", "上传失败");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
119
src/main/java/com/petstore/controller/ReportController.java
Normal file
119
src/main/java/com/petstore/controller/ReportController.java
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
package com.petstore.controller;
|
||||||
|
|
||||||
|
import com.petstore.entity.Report;
|
||||||
|
import com.petstore.entity.Store;
|
||||||
|
import com.petstore.service.ReportService;
|
||||||
|
import com.petstore.service.StoreService;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/report")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@CrossOrigin
|
||||||
|
public class ReportController {
|
||||||
|
private final ReportService reportService;
|
||||||
|
private final StoreService storeService;
|
||||||
|
|
||||||
|
@Value("${app.base-url:http://localhost:8080}")
|
||||||
|
private String baseUrl;
|
||||||
|
|
||||||
|
private String fullUrl(String path) {
|
||||||
|
if (path == null || path.isEmpty()) return path;
|
||||||
|
if (path.startsWith("http")) return path;
|
||||||
|
return baseUrl + path;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/create")
|
||||||
|
public Map<String, Object> create(@RequestBody Report report) {
|
||||||
|
System.out.println(">>> Report create received: appointmentId=" + report.getAppointmentId() + ", userId=" + report.getUserId() + ", before=" + report.getBeforePhoto());
|
||||||
|
Report created = reportService.create(report);
|
||||||
|
|
||||||
|
Map<String, Object> result = new HashMap<>();
|
||||||
|
result.put("code", 200);
|
||||||
|
result.put("message", "提交成功");
|
||||||
|
result.put("data", Map.of(
|
||||||
|
"reportToken", created.getReportToken(),
|
||||||
|
"reportId", created.getId()
|
||||||
|
));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/list")
|
||||||
|
public Map<String, Object> list(@RequestParam(required = false) Long storeId,
|
||||||
|
@RequestParam(required = false) Long userId) {
|
||||||
|
List<Report> reports = reportService.list(storeId, userId);
|
||||||
|
// 附加技师名称,并补全图片URL
|
||||||
|
List<Map<String, Object>> data = reports.stream().map(r -> {
|
||||||
|
Map<String, Object> item = new HashMap<>();
|
||||||
|
item.put("id", r.getId());
|
||||||
|
item.put("appointmentId", r.getAppointmentId());
|
||||||
|
item.put("petName", r.getPetName());
|
||||||
|
item.put("serviceType", r.getServiceType());
|
||||||
|
item.put("appointmentTime", r.getAppointmentTime());
|
||||||
|
item.put("staffName", r.getStaffName());
|
||||||
|
item.put("reportToken", r.getReportToken());
|
||||||
|
item.put("createTime", r.getCreateTime());
|
||||||
|
item.put("beforePhoto", fullUrl(r.getBeforePhoto()));
|
||||||
|
item.put("afterPhoto", fullUrl(r.getAfterPhoto()));
|
||||||
|
item.put("storeId", r.getStoreId());
|
||||||
|
item.put("userId", r.getUserId());
|
||||||
|
return item;
|
||||||
|
}).collect(Collectors.toList());
|
||||||
|
return Map.of("code", 200, "data", data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/get")
|
||||||
|
public Map<String, Object> getByAppointmentId(@RequestParam(required = false) Long appointmentId,
|
||||||
|
@RequestParam(required = false) String token) {
|
||||||
|
Report report = null;
|
||||||
|
if (token != null && !token.isEmpty()) {
|
||||||
|
report = reportService.getByToken(token);
|
||||||
|
} else if (appointmentId != null) {
|
||||||
|
report = reportService.getByAppointmentId(appointmentId);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, Object> result = new HashMap<>();
|
||||||
|
if (report != null) {
|
||||||
|
// 附加店铺信息
|
||||||
|
Store store = null;
|
||||||
|
if (report.getStoreId() != null) {
|
||||||
|
store = storeService.findById(report.getStoreId());
|
||||||
|
}
|
||||||
|
Map<String, Object> data = new HashMap<>();
|
||||||
|
data.put("id", report.getId());
|
||||||
|
data.put("appointmentId", report.getAppointmentId());
|
||||||
|
data.put("beforePhoto", report.getBeforePhoto());
|
||||||
|
data.put("afterPhoto", report.getAfterPhoto());
|
||||||
|
data.put("remark", report.getRemark());
|
||||||
|
data.put("userId", report.getUserId());
|
||||||
|
data.put("storeId", report.getStoreId());
|
||||||
|
data.put("reportToken", report.getReportToken());
|
||||||
|
data.put("petName", report.getPetName());
|
||||||
|
data.put("serviceType", report.getServiceType());
|
||||||
|
data.put("appointmentTime", report.getAppointmentTime());
|
||||||
|
data.put("staffName", report.getStaffName());
|
||||||
|
data.put("createTime", report.getCreateTime());
|
||||||
|
if (store != null) {
|
||||||
|
Map<String, Object> storeInfo = new HashMap<>();
|
||||||
|
storeInfo.put("name", store.getName());
|
||||||
|
storeInfo.put("logo", store.getLogo());
|
||||||
|
storeInfo.put("phone", store.getPhone());
|
||||||
|
storeInfo.put("address", store.getAddress());
|
||||||
|
data.put("store", storeInfo);
|
||||||
|
}
|
||||||
|
result.put("code", 200);
|
||||||
|
result.put("data", data);
|
||||||
|
} else {
|
||||||
|
result.put("code", 404);
|
||||||
|
result.put("message", "报告不存在");
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,52 @@
|
|||||||
|
package com.petstore.controller;
|
||||||
|
|
||||||
|
import com.petstore.entity.ServiceType;
|
||||||
|
import com.petstore.service.ServiceTypeService;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/service-type")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@CrossOrigin
|
||||||
|
public class ServiceTypeController {
|
||||||
|
private final ServiceTypeService serviceTypeService;
|
||||||
|
|
||||||
|
@GetMapping("/list")
|
||||||
|
public Map<String, Object> list(@RequestParam Long storeId) {
|
||||||
|
List<ServiceType> list = serviceTypeService.getByStoreId(storeId);
|
||||||
|
return Map.of("code", 200, "data", list);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/create")
|
||||||
|
public Map<String, Object> create(@RequestBody Map<String, Object> params) {
|
||||||
|
Long storeId = Long.valueOf(params.get("storeId").toString());
|
||||||
|
String name = params.get("name").toString();
|
||||||
|
ServiceType created = serviceTypeService.create(storeId, name);
|
||||||
|
return Map.of("code", 200, "data", created);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/update")
|
||||||
|
public Map<String, Object> update(@RequestBody Map<String, Object> params) {
|
||||||
|
Long id = Long.valueOf(params.get("id").toString());
|
||||||
|
String name = params.get("name").toString();
|
||||||
|
ServiceType updated = serviceTypeService.update(id, name);
|
||||||
|
return Map.of("code", 200, "data", updated);
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping("/delete")
|
||||||
|
public Map<String, Object> delete(@RequestParam Long id) {
|
||||||
|
serviceTypeService.delete(id);
|
||||||
|
return Map.of("code", 200, "message", "删除成功");
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/init")
|
||||||
|
public Map<String, Object> init() {
|
||||||
|
serviceTypeService.initDefaults();
|
||||||
|
return Map.of("code", 200, "message", "初始化成功");
|
||||||
|
}
|
||||||
|
}
|
||||||
45
src/main/java/com/petstore/controller/SmsController.java
Normal file
45
src/main/java/com/petstore/controller/SmsController.java
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
package com.petstore.controller;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/sms")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@CrossOrigin
|
||||||
|
public class SmsController {
|
||||||
|
|
||||||
|
// TODO: 接入真实的短信服务商(阿里云/腾讯云/华为云等)
|
||||||
|
// 这里用演示模式,随机生成6位验证码
|
||||||
|
|
||||||
|
@PostMapping("/send")
|
||||||
|
public Map<String, Object> send(@RequestBody Map<String, String> params) {
|
||||||
|
String phone = params.get("phone");
|
||||||
|
|
||||||
|
Map<String, Object> result = new HashMap<>();
|
||||||
|
|
||||||
|
if (phone == null || phone.length() != 11) {
|
||||||
|
result.put("code", 400);
|
||||||
|
result.put("message", "手机号格式不正确");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 演示:生成6位验证码
|
||||||
|
String code = String.format("%06d", new Random().nextInt(999999));
|
||||||
|
|
||||||
|
// TODO: 调用真实短信服务商发送验证码
|
||||||
|
// 阿里云示例: dysmsapi.aliyuncs.com
|
||||||
|
// 腾讯云示例: sms.tencentcloudapi.com
|
||||||
|
|
||||||
|
System.out.println("【宠伴生活馆】验证码:" + code + ",您正在登录,5分钟内有效。");
|
||||||
|
|
||||||
|
result.put("code", 200);
|
||||||
|
result.put("message", "发送成功");
|
||||||
|
result.put("data", Map.of("code", code)); // 演示用,实际生产环境不应返回code
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
65
src/main/java/com/petstore/controller/StoreController.java
Normal file
65
src/main/java/com/petstore/controller/StoreController.java
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
package com.petstore.controller;
|
||||||
|
|
||||||
|
import com.petstore.entity.Store;
|
||||||
|
import com.petstore.service.StoreService;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/store")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@CrossOrigin
|
||||||
|
public class StoreController {
|
||||||
|
private final StoreService storeService;
|
||||||
|
|
||||||
|
@PostMapping("/register")
|
||||||
|
public Map<String, Object> register(@RequestBody Store store) {
|
||||||
|
Store created = storeService.create(store);
|
||||||
|
Map<String, Object> result = new HashMap<>();
|
||||||
|
result.put("code", 200);
|
||||||
|
result.put("message", "注册成功");
|
||||||
|
result.put("data", created);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/get")
|
||||||
|
public Map<String, Object> get(@RequestParam Long id) {
|
||||||
|
Store store = storeService.findById(id);
|
||||||
|
Map<String, Object> result = new HashMap<>();
|
||||||
|
if (store != null) {
|
||||||
|
result.put("code", 200);
|
||||||
|
result.put("data", store);
|
||||||
|
} else {
|
||||||
|
result.put("code", 404);
|
||||||
|
result.put("message", "店铺不存在");
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/update")
|
||||||
|
public Map<String, Object> update(@RequestBody Store store) {
|
||||||
|
Store updated = storeService.update(store);
|
||||||
|
Map<String, Object> result = new HashMap<>();
|
||||||
|
result.put("code", 200);
|
||||||
|
result.put("message", "更新成功");
|
||||||
|
result.put("data", updated);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/invite-code")
|
||||||
|
public Map<String, Object> getByInviteCode(@RequestParam String code) {
|
||||||
|
Store store = storeService.findByInviteCode(code);
|
||||||
|
Map<String, Object> result = new HashMap<>();
|
||||||
|
if (store != null) {
|
||||||
|
result.put("code", 200);
|
||||||
|
result.put("data", store);
|
||||||
|
} else {
|
||||||
|
result.put("code", 404);
|
||||||
|
result.put("message", "邀请码无效");
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
85
src/main/java/com/petstore/controller/UserController.java
Normal file
85
src/main/java/com/petstore/controller/UserController.java
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
package com.petstore.controller;
|
||||||
|
|
||||||
|
import com.petstore.entity.User;
|
||||||
|
import com.petstore.service.UserService;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/user")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@CrossOrigin
|
||||||
|
public class UserController {
|
||||||
|
private final UserService userService;
|
||||||
|
|
||||||
|
/** 老板注册店铺 */
|
||||||
|
@PostMapping("/register-boss")
|
||||||
|
public Map<String, Object> registerBoss(@RequestBody Map<String, String> params) {
|
||||||
|
String storeName = params.get("storeName");
|
||||||
|
String bossName = params.get("bossName");
|
||||||
|
String phone = params.get("phone");
|
||||||
|
String password = params.get("password");
|
||||||
|
return userService.registerBoss(storeName, bossName, phone, password);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 登录(老板/员工) */
|
||||||
|
@PostMapping("/login")
|
||||||
|
public Map<String, Object> login(@RequestBody Map<String, String> params) {
|
||||||
|
String phone = params.get("phone");
|
||||||
|
String code = params.get("code");
|
||||||
|
return userService.login(phone, code);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 员工注册(邀请码方式) */
|
||||||
|
@PostMapping("/register-staff")
|
||||||
|
public Map<String, Object> registerStaff(@RequestBody Map<String, String> params) {
|
||||||
|
String phone = params.get("phone");
|
||||||
|
String password = params.get("password");
|
||||||
|
String name = params.get("name");
|
||||||
|
String inviteCode = params.get("inviteCode");
|
||||||
|
return userService.registerStaff(phone, password, name, inviteCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 老板:创建员工 */
|
||||||
|
@PostMapping("/create-staff")
|
||||||
|
public Map<String, Object> createStaff(@RequestBody Map<String, Object> params) {
|
||||||
|
Long storeId = Long.valueOf(params.get("storeId").toString());
|
||||||
|
String name = params.get("name").toString();
|
||||||
|
String phone = params.get("phone").toString();
|
||||||
|
return userService.createStaff(storeId, name, phone);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 老板:员工列表 */
|
||||||
|
@GetMapping("/staff-list")
|
||||||
|
public Map<String, Object> staffList(@RequestParam Long storeId) {
|
||||||
|
List<User> list = userService.getStaffList(storeId);
|
||||||
|
// 不返回密码
|
||||||
|
list.forEach(u -> u.setPassword(null));
|
||||||
|
return Map.of("code", 200, "data", list);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 老板:删除员工 */
|
||||||
|
@DeleteMapping("/staff")
|
||||||
|
public Map<String, Object> deleteStaff(@RequestParam Long staffId) {
|
||||||
|
userService.deleteStaff(staffId);
|
||||||
|
return Map.of("code", 200, "message", "删除成功");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 获取用户信息 */
|
||||||
|
@GetMapping("/info")
|
||||||
|
public Map<String, Object> info(@RequestParam Long userId) {
|
||||||
|
User user = userService.findById(userId);
|
||||||
|
if (user != null) user.setPassword(null);
|
||||||
|
return Map.of("code", 200, "data", user);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 更新用户信息(头像/姓名/手机号) */
|
||||||
|
@PutMapping("/update")
|
||||||
|
public Map<String, Object> updateUser(@RequestBody Map<String, Object> params) {
|
||||||
|
return userService.updateUser(params);
|
||||||
|
}
|
||||||
|
}
|
||||||
49
src/main/java/com/petstore/controller/WechatController.java
Normal file
49
src/main/java/com/petstore/controller/WechatController.java
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
package com.petstore.controller;
|
||||||
|
|
||||||
|
import com.petstore.config.WechatConfig;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/wechat")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@CrossOrigin
|
||||||
|
public class WechatController {
|
||||||
|
private final WechatConfig wechatConfig;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取微信授权跳转地址
|
||||||
|
*/
|
||||||
|
@GetMapping("/authorize")
|
||||||
|
public Map<String, Object> getAuthorizeUrl() {
|
||||||
|
Map<String, Object> result = new HashMap<>();
|
||||||
|
result.put("code", 200);
|
||||||
|
result.put("data", wechatConfig.getAuthorizeUrl());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信授权回调
|
||||||
|
* 通过 code 换取 access_token,返回用户信息
|
||||||
|
*/
|
||||||
|
@GetMapping("/callback")
|
||||||
|
public Map<String, Object> callback(@RequestParam String code, @RequestParam String state) {
|
||||||
|
Map<String, Object> result = new HashMap<>();
|
||||||
|
|
||||||
|
// TODO: 通过 code 调用微信接口换取 openid 和 access_token
|
||||||
|
// https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code
|
||||||
|
|
||||||
|
// 演示用:直接返回成功
|
||||||
|
result.put("code", 200);
|
||||||
|
result.put("message", "微信授权成功");
|
||||||
|
result.put("data", Map.of(
|
||||||
|
"openid", "demo_openid_" + code,
|
||||||
|
"nickname", "微信用户",
|
||||||
|
"avatar", ""
|
||||||
|
));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
40
src/main/java/com/petstore/entity/Appointment.java
Normal file
40
src/main/java/com/petstore/entity/Appointment.java
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
package com.petstore.entity;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import lombok.Data;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Entity
|
||||||
|
@Table(name = "t_appointment")
|
||||||
|
public class Appointment {
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
private String petName;
|
||||||
|
private String petType;
|
||||||
|
private String serviceType;
|
||||||
|
private LocalDateTime appointmentTime;
|
||||||
|
|
||||||
|
/** 状态: new/doing/done/cancel */
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
@Column(name = "store_id")
|
||||||
|
private Long storeId;
|
||||||
|
|
||||||
|
@Column(name = "user_id")
|
||||||
|
private Long userId;
|
||||||
|
|
||||||
|
/** 技师ID,开始服务时赋值 */
|
||||||
|
@Column(name = "assigned_user_id")
|
||||||
|
private Long assignedUserId;
|
||||||
|
|
||||||
|
private String remark;
|
||||||
|
|
||||||
|
@Column(name = "create_time")
|
||||||
|
private LocalDateTime createTime;
|
||||||
|
|
||||||
|
@Column(name = "update_time")
|
||||||
|
private LocalDateTime updateTime;
|
||||||
|
}
|
||||||
47
src/main/java/com/petstore/entity/Report.java
Normal file
47
src/main/java/com/petstore/entity/Report.java
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
package com.petstore.entity;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Entity
|
||||||
|
@Table(name = "t_report")
|
||||||
|
public class Report {
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@JsonProperty("appointmentId")
|
||||||
|
@Column(name = "appointment_id")
|
||||||
|
private Long appointmentId;
|
||||||
|
|
||||||
|
@Column(name = "before_photo", columnDefinition = "TEXT")
|
||||||
|
private String beforePhoto;
|
||||||
|
|
||||||
|
@Column(name = "after_photo", columnDefinition = "TEXT")
|
||||||
|
private String afterPhoto;
|
||||||
|
|
||||||
|
private String remark;
|
||||||
|
|
||||||
|
@Column(name = "user_id")
|
||||||
|
private Long userId;
|
||||||
|
|
||||||
|
@Column(name = "store_id")
|
||||||
|
private Long storeId;
|
||||||
|
|
||||||
|
@Column(name = "report_token")
|
||||||
|
private String reportToken;
|
||||||
|
|
||||||
|
private String petName;
|
||||||
|
private String serviceType;
|
||||||
|
private LocalDateTime appointmentTime;
|
||||||
|
private String staffName;
|
||||||
|
|
||||||
|
@Column(name = "create_time")
|
||||||
|
private LocalDateTime createTime;
|
||||||
|
|
||||||
|
@Column(name = "update_time")
|
||||||
|
private LocalDateTime updateTime;
|
||||||
|
}
|
||||||
22
src/main/java/com/petstore/entity/ServiceType.java
Normal file
22
src/main/java/com/petstore/entity/ServiceType.java
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
package com.petstore.entity;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import lombok.Data;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Entity
|
||||||
|
@Table(name = "t_service_type")
|
||||||
|
public class ServiceType {
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/** 店铺ID,NULL表示系统默认 */
|
||||||
|
private Long storeId;
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@Column(name = "create_time")
|
||||||
|
private LocalDateTime createTime;
|
||||||
|
}
|
||||||
36
src/main/java/com/petstore/entity/Store.java
Normal file
36
src/main/java/com/petstore/entity/Store.java
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
package com.petstore.entity;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import lombok.Data;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Entity
|
||||||
|
@Table(name = "t_store")
|
||||||
|
public class Store {
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
private String logo;
|
||||||
|
private String phone;
|
||||||
|
private String address;
|
||||||
|
/** 地图选点纬度(WGS84 / 各端与地图接口一致) */
|
||||||
|
private Double latitude;
|
||||||
|
/** 地图选点经度 */
|
||||||
|
private Double longitude;
|
||||||
|
private String intro;
|
||||||
|
|
||||||
|
@Column(name = "owner_id")
|
||||||
|
private Long ownerId;
|
||||||
|
|
||||||
|
@Column(name = "invite_code")
|
||||||
|
private String inviteCode;
|
||||||
|
|
||||||
|
@Column(name = "create_time")
|
||||||
|
private LocalDateTime createTime;
|
||||||
|
|
||||||
|
@Column(name = "update_time")
|
||||||
|
private LocalDateTime updateTime;
|
||||||
|
}
|
||||||
32
src/main/java/com/petstore/entity/User.java
Normal file
32
src/main/java/com/petstore/entity/User.java
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
package com.petstore.entity;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import lombok.Data;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Entity
|
||||||
|
@Table(name = "t_user")
|
||||||
|
public class User {
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
private String username;
|
||||||
|
private String password;
|
||||||
|
private String name;
|
||||||
|
private String phone;
|
||||||
|
private String avatar;
|
||||||
|
|
||||||
|
@Column(name = "store_id")
|
||||||
|
private Long storeId;
|
||||||
|
|
||||||
|
/** 角色:boss / staff / customer */
|
||||||
|
private String role;
|
||||||
|
|
||||||
|
@Column(name = "create_time")
|
||||||
|
private LocalDateTime createTime;
|
||||||
|
|
||||||
|
@Column(name = "update_time")
|
||||||
|
private LocalDateTime updateTime;
|
||||||
|
}
|
||||||
13
src/main/java/com/petstore/mapper/AppointmentMapper.java
Normal file
13
src/main/java/com/petstore/mapper/AppointmentMapper.java
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package com.petstore.mapper;
|
||||||
|
|
||||||
|
import com.petstore.entity.Appointment;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface AppointmentMapper extends JpaRepository<Appointment, Long> {
|
||||||
|
List<Appointment> findByUserId(Long userId);
|
||||||
|
List<Appointment> findByUserIdAndStatus(Long userId, String status);
|
||||||
|
List<Appointment> findByStoreId(Long storeId);
|
||||||
|
List<Appointment> findByStoreIdAndStatus(Long storeId, String status);
|
||||||
|
}
|
||||||
7
src/main/java/com/petstore/mapper/ReportMapper.java
Normal file
7
src/main/java/com/petstore/mapper/ReportMapper.java
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
package com.petstore.mapper;
|
||||||
|
|
||||||
|
import com.petstore.entity.Report;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
|
public interface ReportMapper extends JpaRepository<Report, Long> {
|
||||||
|
}
|
||||||
11
src/main/java/com/petstore/mapper/ServiceTypeMapper.java
Normal file
11
src/main/java/com/petstore/mapper/ServiceTypeMapper.java
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package com.petstore.mapper;
|
||||||
|
|
||||||
|
import com.petstore.entity.ServiceType;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface ServiceTypeMapper extends JpaRepository<ServiceType, Long> {
|
||||||
|
List<ServiceType> findByStoreIdOrStoreIdIsNull(Long storeId);
|
||||||
|
List<ServiceType> findByStoreId(Long storeId);
|
||||||
|
}
|
||||||
8
src/main/java/com/petstore/mapper/StoreMapper.java
Normal file
8
src/main/java/com/petstore/mapper/StoreMapper.java
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
package com.petstore.mapper;
|
||||||
|
|
||||||
|
import com.petstore.entity.Store;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
|
public interface StoreMapper extends JpaRepository<Store, Long> {
|
||||||
|
Store findByInviteCode(String inviteCode);
|
||||||
|
}
|
||||||
12
src/main/java/com/petstore/mapper/UserMapper.java
Normal file
12
src/main/java/com/petstore/mapper/UserMapper.java
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
package com.petstore.mapper;
|
||||||
|
|
||||||
|
import com.petstore.entity.User;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface UserMapper extends JpaRepository<User, Long> {
|
||||||
|
User findByUsername(String username);
|
||||||
|
User findByPhone(String phone);
|
||||||
|
List<User> findByStoreId(Long storeId);
|
||||||
|
}
|
||||||
63
src/main/java/com/petstore/service/AppointmentService.java
Normal file
63
src/main/java/com/petstore/service/AppointmentService.java
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
package com.petstore.service;
|
||||||
|
|
||||||
|
import com.petstore.entity.Appointment;
|
||||||
|
import com.petstore.mapper.AppointmentMapper;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class AppointmentService {
|
||||||
|
private final AppointmentMapper appointmentMapper;
|
||||||
|
|
||||||
|
// 员工查看自己的预约
|
||||||
|
public List<Appointment> getByUserId(Long userId) {
|
||||||
|
return appointmentMapper.findByUserId(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 老板查看本店所有预约
|
||||||
|
public List<Appointment> getByStoreId(Long storeId) {
|
||||||
|
return appointmentMapper.findByStoreId(storeId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 员工按状态查
|
||||||
|
public List<Appointment> getByUserIdAndStatus(Long userId, String status) {
|
||||||
|
return appointmentMapper.findByUserIdAndStatus(userId, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 老板按状态查
|
||||||
|
public List<Appointment> getByStoreIdAndStatus(Long storeId, String status) {
|
||||||
|
return appointmentMapper.findByStoreIdAndStatus(storeId, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Appointment create(Appointment appointment) {
|
||||||
|
appointment.setCreateTime(LocalDateTime.now());
|
||||||
|
appointment.setUpdateTime(LocalDateTime.now());
|
||||||
|
return appointmentMapper.save(appointment);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Appointment updateStatus(Long id, String status) {
|
||||||
|
Appointment appointment = appointmentMapper.findById(id).orElse(null);
|
||||||
|
if (appointment != null) {
|
||||||
|
appointment.setStatus(status);
|
||||||
|
appointment.setUpdateTime(LocalDateTime.now());
|
||||||
|
return appointmentMapper.save(appointment);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 开始服务:状态变为进行中,同时指定技师为当前用户 */
|
||||||
|
public Appointment startService(Long appointmentId, Long staffUserId) {
|
||||||
|
Appointment appointment = appointmentMapper.findById(appointmentId).orElse(null);
|
||||||
|
if (appointment != null) {
|
||||||
|
appointment.setStatus("doing");
|
||||||
|
appointment.setAssignedUserId(staffUserId);
|
||||||
|
appointment.setUpdateTime(LocalDateTime.now());
|
||||||
|
return appointmentMapper.save(appointment);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
86
src/main/java/com/petstore/service/ReportService.java
Normal file
86
src/main/java/com/petstore/service/ReportService.java
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
package com.petstore.service;
|
||||||
|
|
||||||
|
import com.petstore.entity.Appointment;
|
||||||
|
import com.petstore.entity.Report;
|
||||||
|
import com.petstore.entity.User;
|
||||||
|
import com.petstore.mapper.AppointmentMapper;
|
||||||
|
import com.petstore.mapper.ReportMapper;
|
||||||
|
import com.petstore.mapper.UserMapper;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class ReportService {
|
||||||
|
private final ReportMapper reportMapper;
|
||||||
|
private final AppointmentMapper appointmentMapper;
|
||||||
|
private final UserMapper userMapper;
|
||||||
|
|
||||||
|
public Report create(Report report) {
|
||||||
|
// 生成唯一令牌
|
||||||
|
report.setReportToken(UUID.randomUUID().toString().replace("-", ""));
|
||||||
|
|
||||||
|
// 填充冗余字段,并自动完成预约
|
||||||
|
if (report.getAppointmentId() != null) {
|
||||||
|
Appointment appt = appointmentMapper.findById(report.getAppointmentId()).orElse(null);
|
||||||
|
if (appt != null) {
|
||||||
|
report.setPetName(appt.getPetName());
|
||||||
|
report.setServiceType(appt.getServiceType());
|
||||||
|
report.setAppointmentTime(appt.getAppointmentTime());
|
||||||
|
report.setStoreId(appt.getStoreId());
|
||||||
|
// 技师取预约分配的技师(开始服务时指定的)
|
||||||
|
if (appt.getAssignedUserId() != null) {
|
||||||
|
User staff = userMapper.findById(appt.getAssignedUserId()).orElse(null);
|
||||||
|
if (staff != null) {
|
||||||
|
report.setUserId(staff.getId());
|
||||||
|
report.setStaffName(staff.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 填写完报告,自动标记预约为已完成
|
||||||
|
appt.setStatus("done");
|
||||||
|
appt.setUpdateTime(LocalDateTime.now());
|
||||||
|
appointmentMapper.save(appt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 如果预约没分配技师,则用当前操作人
|
||||||
|
if (report.getUserId() != null && report.getStaffName() == null) {
|
||||||
|
User staff = userMapper.findById(report.getUserId()).orElse(null);
|
||||||
|
if (staff != null) {
|
||||||
|
report.setStaffName(staff.getName());
|
||||||
|
if (report.getStoreId() == null) {
|
||||||
|
report.setStoreId(staff.getStoreId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
report.setCreateTime(LocalDateTime.now());
|
||||||
|
report.setUpdateTime(LocalDateTime.now());
|
||||||
|
return reportMapper.save(report);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Report getByAppointmentId(Long appointmentId) {
|
||||||
|
return reportMapper.findAll().stream()
|
||||||
|
.filter(r -> r.getAppointmentId().equals(appointmentId))
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Report getByToken(String token) {
|
||||||
|
return reportMapper.findAll().stream()
|
||||||
|
.filter(r -> token.equals(r.getReportToken()))
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Report> list(Long storeId, Long userId) {
|
||||||
|
return reportMapper.findAll().stream()
|
||||||
|
.filter(r -> storeId == null || storeId.equals(r.getStoreId()))
|
||||||
|
.filter(r -> userId == null || userId.equals(r.getUserId()))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
}
|
||||||
59
src/main/java/com/petstore/service/ServiceTypeService.java
Normal file
59
src/main/java/com/petstore/service/ServiceTypeService.java
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
package com.petstore.service;
|
||||||
|
|
||||||
|
import com.petstore.entity.ServiceType;
|
||||||
|
import com.petstore.mapper.ServiceTypeMapper;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class ServiceTypeService {
|
||||||
|
private final ServiceTypeMapper serviceTypeMapper;
|
||||||
|
|
||||||
|
/** 获取服务类型(系统默认 + 当前店铺自定义) */
|
||||||
|
public List<ServiceType> getByStoreId(Long storeId) {
|
||||||
|
return serviceTypeMapper.findByStoreIdOrStoreIdIsNull(storeId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 老板新增服务类型 */
|
||||||
|
public ServiceType create(Long storeId, String name) {
|
||||||
|
ServiceType st = new ServiceType();
|
||||||
|
st.setStoreId(storeId);
|
||||||
|
st.setName(name);
|
||||||
|
st.setCreateTime(LocalDateTime.now());
|
||||||
|
return serviceTypeMapper.save(st);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 老板编辑服务类型 */
|
||||||
|
public ServiceType update(Long id, String name) {
|
||||||
|
ServiceType st = serviceTypeMapper.findById(id).orElse(null);
|
||||||
|
if (st != null) {
|
||||||
|
st.setName(name);
|
||||||
|
serviceTypeMapper.save(st);
|
||||||
|
}
|
||||||
|
return st;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 老板删除服务类型(仅能删除自己店铺的) */
|
||||||
|
public void delete(Long id) {
|
||||||
|
serviceTypeMapper.deleteById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 初始化系统默认服务类型(如果不存在) */
|
||||||
|
public void initDefaults() {
|
||||||
|
List<ServiceType> defaults = serviceTypeMapper.findByStoreIdOrStoreIdIsNull(null);
|
||||||
|
if (defaults.isEmpty()) {
|
||||||
|
String[] names = {"洗澡", "美容", "洗澡+美容", "剪指甲", "驱虫"};
|
||||||
|
for (String name : names) {
|
||||||
|
ServiceType st = new ServiceType();
|
||||||
|
st.setStoreId(null);
|
||||||
|
st.setName(name);
|
||||||
|
st.setCreateTime(LocalDateTime.now());
|
||||||
|
serviceTypeMapper.save(st);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
39
src/main/java/com/petstore/service/StoreService.java
Normal file
39
src/main/java/com/petstore/service/StoreService.java
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
package com.petstore.service;
|
||||||
|
|
||||||
|
import com.petstore.entity.Store;
|
||||||
|
import com.petstore.mapper.StoreMapper;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class StoreService {
|
||||||
|
private final StoreMapper storeMapper;
|
||||||
|
|
||||||
|
public Store create(Store store) {
|
||||||
|
store.setInviteCode(generateInviteCode());
|
||||||
|
store.setCreateTime(LocalDateTime.now());
|
||||||
|
store.setUpdateTime(LocalDateTime.now());
|
||||||
|
return storeMapper.save(store);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Store findById(Long id) {
|
||||||
|
return storeMapper.findById(id).orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Store findByInviteCode(String code) {
|
||||||
|
return storeMapper.findByInviteCode(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Store update(Store store) {
|
||||||
|
store.setUpdateTime(LocalDateTime.now());
|
||||||
|
return storeMapper.save(store);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String generateInviteCode() {
|
||||||
|
return UUID.randomUUID().toString().replace("-", "").substring(0, 8).toUpperCase();
|
||||||
|
}
|
||||||
|
}
|
||||||
168
src/main/java/com/petstore/service/UserService.java
Normal file
168
src/main/java/com/petstore/service/UserService.java
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
package com.petstore.service;
|
||||||
|
|
||||||
|
import com.petstore.entity.Store;
|
||||||
|
import com.petstore.entity.User;
|
||||||
|
import com.petstore.mapper.StoreMapper;
|
||||||
|
import com.petstore.mapper.UserMapper;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class UserService {
|
||||||
|
private final UserMapper userMapper;
|
||||||
|
private final StoreMapper storeMapper;
|
||||||
|
|
||||||
|
public Map<String, Object> registerBoss(String storeName, String bossName, String phone, String password) {
|
||||||
|
if (userMapper.findByPhone(phone) != null) {
|
||||||
|
return Map.of("code", 400, "message", "手机号已注册");
|
||||||
|
}
|
||||||
|
|
||||||
|
Store store = new Store();
|
||||||
|
store.setName(storeName);
|
||||||
|
store.setPhone(phone);
|
||||||
|
store.setOwnerId(0L);
|
||||||
|
store.setInviteCode(UUID.randomUUID().toString().replace("-", "").substring(0, 8).toUpperCase());
|
||||||
|
store.setCreateTime(LocalDateTime.now());
|
||||||
|
store.setUpdateTime(LocalDateTime.now());
|
||||||
|
store = storeMapper.save(store);
|
||||||
|
|
||||||
|
User boss = new User();
|
||||||
|
boss.setUsername(phone);
|
||||||
|
boss.setName(bossName);
|
||||||
|
boss.setPhone(phone);
|
||||||
|
boss.setPassword(password);
|
||||||
|
boss.setStoreId(store.getId());
|
||||||
|
boss.setRole("boss");
|
||||||
|
boss.setCreateTime(LocalDateTime.now());
|
||||||
|
boss.setUpdateTime(LocalDateTime.now());
|
||||||
|
boss = userMapper.save(boss);
|
||||||
|
|
||||||
|
store.setOwnerId(boss.getId());
|
||||||
|
storeMapper.save(store);
|
||||||
|
|
||||||
|
Map<String, Object> data = new HashMap<>();
|
||||||
|
data.put("user", boss);
|
||||||
|
data.put("store", store);
|
||||||
|
return Map.of("code", 200, "message", "注册成功", "data", data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Object> login(String phone, String code) {
|
||||||
|
// 演示模式:验证码 123456 万能
|
||||||
|
if (!"123456".equals(code)) {
|
||||||
|
return Map.of("code", 401, "message", "验证码错误");
|
||||||
|
}
|
||||||
|
User user = userMapper.findByPhone(phone);
|
||||||
|
if (user == null) {
|
||||||
|
// 自动注册为 C 端用户 (customer)
|
||||||
|
user = new User();
|
||||||
|
user.setUsername(phone);
|
||||||
|
user.setPhone(phone);
|
||||||
|
user.setName("微信用户" + phone.substring(7));
|
||||||
|
user.setRole("customer");
|
||||||
|
user.setCreateTime(LocalDateTime.now());
|
||||||
|
user.setUpdateTime(LocalDateTime.now());
|
||||||
|
user = userMapper.save(user);
|
||||||
|
}
|
||||||
|
Store store = null;
|
||||||
|
if (user.getStoreId() != null) {
|
||||||
|
store = storeMapper.findById(user.getStoreId()).orElse(null);
|
||||||
|
}
|
||||||
|
Map<String, Object> data = new HashMap<>();
|
||||||
|
data.put("user", user);
|
||||||
|
data.put("store", store);
|
||||||
|
return Map.of("code", 200, "message", "登录成功", "data", data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Object> registerStaff(String phone, String password, String name, String inviteCode) {
|
||||||
|
if (userMapper.findByPhone(phone) != null) {
|
||||||
|
return Map.of("code", 400, "message", "手机号已注册");
|
||||||
|
}
|
||||||
|
Store store = storeMapper.findByInviteCode(inviteCode);
|
||||||
|
if (store == null) {
|
||||||
|
return Map.of("code", 400, "message", "邀请码无效");
|
||||||
|
}
|
||||||
|
User staff = new User();
|
||||||
|
staff.setUsername(phone);
|
||||||
|
staff.setPhone(phone);
|
||||||
|
staff.setPassword(password);
|
||||||
|
staff.setName(name);
|
||||||
|
staff.setStoreId(store.getId());
|
||||||
|
staff.setRole("staff");
|
||||||
|
staff.setCreateTime(LocalDateTime.now());
|
||||||
|
staff.setUpdateTime(LocalDateTime.now());
|
||||||
|
staff = userMapper.save(staff);
|
||||||
|
Map<String, Object> data = new HashMap<>();
|
||||||
|
data.put("user", staff);
|
||||||
|
data.put("store", store);
|
||||||
|
return Map.of("code", 200, "message", "注册成功", "data", data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Object> createStaff(Long storeId, String name, String phone) {
|
||||||
|
if (storeId == null) {
|
||||||
|
return Map.of("code", 400, "message", "店铺ID不能为空");
|
||||||
|
}
|
||||||
|
if (userMapper.findByPhone(phone) != null) {
|
||||||
|
return Map.of("code", 400, "message", "手机号已存在");
|
||||||
|
}
|
||||||
|
String pwd = String.format("%06d", (int)(Math.random() * 999999));
|
||||||
|
User staff = new User();
|
||||||
|
staff.setUsername(phone);
|
||||||
|
staff.setName(name);
|
||||||
|
staff.setPhone(phone);
|
||||||
|
staff.setPassword(pwd);
|
||||||
|
staff.setStoreId(storeId);
|
||||||
|
staff.setRole("staff");
|
||||||
|
staff.setCreateTime(LocalDateTime.now());
|
||||||
|
staff.setUpdateTime(LocalDateTime.now());
|
||||||
|
staff = userMapper.save(staff);
|
||||||
|
return Map.of("code", 200, "message", "创建成功,初始密码:" + pwd, "data", staff);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<User> getStaffList(Long storeId) {
|
||||||
|
return userMapper.findByStoreId(storeId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deleteStaff(Long staffId) {
|
||||||
|
userMapper.deleteById(staffId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public User findById(Long id) {
|
||||||
|
return userMapper.findById(id).orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Object> updateUser(Map<String, Object> params) {
|
||||||
|
Long userId = Long.valueOf(params.get("id").toString());
|
||||||
|
User user = userMapper.findById(userId).orElse(null);
|
||||||
|
if (user == null) {
|
||||||
|
return Map.of("code", 404, "message", "用户不存在");
|
||||||
|
}
|
||||||
|
if (params.containsKey("name") && params.get("name") != null) {
|
||||||
|
user.setName(params.get("name").toString());
|
||||||
|
}
|
||||||
|
if (params.containsKey("phone") && params.get("phone") != null) {
|
||||||
|
String newPhone = params.get("phone").toString();
|
||||||
|
// 验证码校验
|
||||||
|
String code = params.get("code") != null ? params.get("code").toString() : "";
|
||||||
|
if (!"123456".equals(code)) {
|
||||||
|
return Map.of("code", 400, "message", "验证码错误");
|
||||||
|
}
|
||||||
|
// 检查手机号是否被占用
|
||||||
|
User existing = userMapper.findByPhone(newPhone);
|
||||||
|
if (existing != null && !existing.getId().equals(userId)) {
|
||||||
|
return Map.of("code", 400, "message", "手机号已被占用");
|
||||||
|
}
|
||||||
|
user.setPhone(newPhone);
|
||||||
|
}
|
||||||
|
if (params.containsKey("avatar") && params.get("avatar") != null) {
|
||||||
|
user.setAvatar(params.get("avatar").toString());
|
||||||
|
}
|
||||||
|
user.setUpdateTime(LocalDateTime.now());
|
||||||
|
userMapper.save(user);
|
||||||
|
user.setPassword(null);
|
||||||
|
return Map.of("code", 200, "message", "更新成功", "data", user);
|
||||||
|
}
|
||||||
|
}
|
||||||
42
src/main/resources/application.yml
Normal file
42
src/main/resources/application.yml
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
spring:
|
||||||
|
application:
|
||||||
|
name: petstore-backend
|
||||||
|
datasource:
|
||||||
|
url: jdbc:mysql://192.144.152.238:3306/petstore?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&autoReconnect=true
|
||||||
|
username: root
|
||||||
|
password: Wabjtam123@
|
||||||
|
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||||
|
hikari:
|
||||||
|
max-lifetime: 1800000 # 30分钟,必须小于MySQL的wait_timeout(默认8小时)
|
||||||
|
connection-test-query: SELECT 1
|
||||||
|
validation-timeout: 3000
|
||||||
|
idle-timeout: 600000 # 10分钟
|
||||||
|
jpa:
|
||||||
|
hibernate:
|
||||||
|
ddl-auto: update
|
||||||
|
show-sql: true
|
||||||
|
properties:
|
||||||
|
hibernate:
|
||||||
|
dialect: org.hibernate.dialect.MySQLDialect
|
||||||
|
format_sql: true
|
||||||
|
servlet:
|
||||||
|
multipart:
|
||||||
|
max-file-size: 10MB
|
||||||
|
|
||||||
|
server:
|
||||||
|
port: 8080
|
||||||
|
servlet:
|
||||||
|
context-path:
|
||||||
|
|
||||||
|
upload:
|
||||||
|
path: uploads
|
||||||
|
|
||||||
|
logging:
|
||||||
|
level:
|
||||||
|
com.petstore: debug
|
||||||
|
|
||||||
|
# 微信登录配置(需替换为实际值)
|
||||||
|
wechat:
|
||||||
|
appid: YOUR_APPID
|
||||||
|
appsecret: YOUR_APPSECRET
|
||||||
|
redirect_uri: http://localhost:8080/api/wechat/callback
|
||||||
Loading…
Reference in New Issue
Block a user