```
chore(project): 添加项目基础配置文件 添加了项目根目录的 .gitignore 文件,包含前端、后端、IDE等各类忽略规则; 新增 README.md 项目说明文档,详细介绍了 PawTrace 服务端的技术栈、目录结构、 模块功能、快速开始指南和版本规划等内容; 初始化 admin-pawtrace 管理端的基础文件,包括 package.json 依赖配置、 index.html 入口文件和 favicon.svg 图标文件。 ```
This commit is contained in:
parent
56c9f0b829
commit
12313b0c1e
|
|
@ -0,0 +1,138 @@
|
|||
# ============================================================
|
||||
# PawTrace 项目根 .gitignore
|
||||
# 子模块: admin-pawtrace (Vite+TS) / uniapp-pawtrace (Uniapp)
|
||||
# server-pawtrace (Spring Boot)
|
||||
# ============================================================
|
||||
|
||||
# -------------------- 通用 --------------------
|
||||
# 日志
|
||||
logs/
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
# 临时/缓存/备份
|
||||
*.tmp
|
||||
*.bak
|
||||
*.swp
|
||||
*.swo
|
||||
*.orig
|
||||
.cache/
|
||||
.temp/
|
||||
.tmp/
|
||||
|
||||
# 系统文件
|
||||
.DS_Store
|
||||
.DS_*
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
Desktop.ini
|
||||
$RECYCLE.BIN/
|
||||
|
||||
# 环境/密钥(绝不入库)
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
*.pem
|
||||
*.key
|
||||
*.p12
|
||||
*.keystore
|
||||
credentials.json
|
||||
secrets.yaml
|
||||
|
||||
# 测试覆盖率
|
||||
coverage/
|
||||
*.lcov
|
||||
.nyc_output/
|
||||
|
||||
# -------------------- 前端通用 --------------------
|
||||
# 依赖
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
bower_components/
|
||||
|
||||
# 构建产物
|
||||
dist/
|
||||
dist-ssr/
|
||||
build/
|
||||
out/
|
||||
.next/
|
||||
.nuxt/
|
||||
.vite/
|
||||
.parcel-cache/
|
||||
.turbo/
|
||||
.turbo-cache/
|
||||
|
||||
# TypeScript
|
||||
*.tsbuildinfo
|
||||
.tscache/
|
||||
|
||||
# -------------------- 管理端 admin-pawtrace (Vite) --------------------
|
||||
.vite/
|
||||
*.local
|
||||
.vite-cache/
|
||||
|
||||
# -------------------- uniapp-pawtrace --------------------
|
||||
# HBuilderX
|
||||
.idea/
|
||||
*.hbuilderx
|
||||
HBuilderX.config
|
||||
.project
|
||||
unpackage/dist/
|
||||
unpackage/release/
|
||||
unpackage/cache/
|
||||
unpackage/resources/
|
||||
|
||||
# 微信开发者工具产物
|
||||
project.private.config.json
|
||||
miniprogram_npm/
|
||||
|
||||
# -------------------- 服务端 server-pawtrace (Maven) --------------------
|
||||
target/
|
||||
build/
|
||||
out/
|
||||
*.class
|
||||
*.jar
|
||||
*.war
|
||||
*.ear
|
||||
hs_err_pid*
|
||||
replay_pid*
|
||||
|
||||
# Maven Wrapper
|
||||
.mvn/wrapper/maven-wrapper.jar
|
||||
!/.mvn/wrapper/maven-wrapper.properties
|
||||
|
||||
# IDE - IntelliJ
|
||||
.idea/
|
||||
*.iml
|
||||
*.iws
|
||||
*.ipr
|
||||
out/
|
||||
|
||||
# IDE - Eclipse / STS
|
||||
.settings/
|
||||
.classpath
|
||||
.project
|
||||
.factorypath
|
||||
.springBeans
|
||||
.sts4-cache/
|
||||
bin/
|
||||
|
||||
# IDE - VSCode(保留 settings.json / 推荐插件)
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
!.vscode/settings.json
|
||||
!.vscode/launch.json
|
||||
|
||||
# IDE - NetBeans
|
||||
nbproject/
|
||||
nbbuild/
|
||||
nbdist/
|
||||
|
||||
# 上传文件(本地运行时生成)
|
||||
upload/
|
||||
uploads/
|
||||
static/upload/
|
||||
|
|
@ -0,0 +1,187 @@
|
|||
# 宠迹 PawTrace · 服务端
|
||||
|
||||
> 宠物成长记录与生活管理后端项目
|
||||
> 配套小程序: [`uniapp-pawtrace`](../uniapp-pawtrace)
|
||||
> 配套管理端: [`admin-pawtrace`](../admin-pawtrace)
|
||||
|
||||
## 一、项目简介
|
||||
|
||||
**宠迹(PawTrace)** 是一款面向个人宠物主的本地化、合规化宠物记录工具。服务端不参与任何用户间互动、不存储敏感人脸/位置信息,所有图片/视频处理均在客户端完成,服务端只负责元数据存取与提醒触达。
|
||||
|
||||
### 设计原则
|
||||
|
||||
- ✅ **本地优先**:服务端只做"陪伴",核心数据存本地,服务端兜底
|
||||
- ✅ **合规底线**:无 UGC 公开、无评论点赞、无支付、无地图 UGC 标注
|
||||
- ✅ **云端可选**:基础版可纯本地使用,登录后提供云端同步与多端访问
|
||||
|
||||
## 二、技术栈
|
||||
|
||||
| 类别 | 选型 |
|
||||
|----------|--------------------------------------------|
|
||||
| 基础框架 | Spring Boot 4.0.6 / Java 21 |
|
||||
| 持久层 | MyBatis-Plus 3.5.15 + MySQL 8.x |
|
||||
| 缓存 | Redis (Lettuce) |
|
||||
| 鉴权 | Sa-Token 1.45.x |
|
||||
| 工具集 | Hutool 5.8.x / Fastjson2 |
|
||||
| 对象映射 | MapStruct 1.6.x |
|
||||
| 微信生态 | WxJava 4.8.x (小程序 / 订阅消息) |
|
||||
| 对象存储 | 七牛云 / 阿里云 OSS / 腾讯云 COS / MinIO / AWS S3 |
|
||||
| 接口文档 | Knife4j 4.6.x (OpenAPI 3) |
|
||||
| 构建 | Maven 3.9+ |
|
||||
|
||||
## 三、目录结构
|
||||
|
||||
```
|
||||
server-pawtrace
|
||||
├── pom.xml
|
||||
├── README.md
|
||||
└── src/main
|
||||
├── java/com/pawtrace/server
|
||||
│ ├── ServerPawtraceApplication.java # 启动类
|
||||
│ ├── common
|
||||
│ │ ├── constant/ # 常量 (CommonConstants / ResultCode)
|
||||
│ │ ├── enums/ # 枚举 (宠物性别 / 日记类型 / 信息卡分类)
|
||||
│ │ ├── exception/ # 全局异常 (BusinessException / GlobalExceptionHandler)
|
||||
│ │ ├── page/ # 分页 (PageRequest / PageResult)
|
||||
│ │ ├── result/ # 统一返回 Result
|
||||
│ │ └── utils/ # 工具类
|
||||
│ ├── config # 配置类 (MyBatis Plus / Redis / Sa-Token / 异步 / 跨域 / Knife4j)
|
||||
│ ├── entity # 基础实体 (BaseEntity)
|
||||
│ └── modules # 业务模块
|
||||
│ ├── user # 用户与鉴权
|
||||
│ ├── pet # 宠物档案 + 体重历史
|
||||
│ ├── diary # 成长日记
|
||||
│ ├── health # 健康医疗(疫苗/驱虫/就诊)
|
||||
│ ├── inventory # 物资管家
|
||||
│ ├── timemachine # 时光机工坊(仅元数据)
|
||||
│ ├── lifecard # 宠物生活信息卡
|
||||
│ ├── feedback # 意见反馈
|
||||
│ ├── reminder # 提醒调度 (Scheduled)
|
||||
│ └── file # 文件上传
|
||||
└── resources
|
||||
├── application.yml # 主配置
|
||||
├── application-dev.yml # 开发环境
|
||||
├── application-prod.yml # 生产环境
|
||||
├── mapper/ # MyBatis XML(可空)
|
||||
└── sql/pawtrace.sql # 初始化 SQL
|
||||
```
|
||||
|
||||
## 四、模块与功能映射
|
||||
|
||||
| 模块 | 路径前缀 | 对应功能设计章节 | 核心接口 |
|
||||
|-------------------||-------------|--------------------------------------------------------|
|
||||
| 用户/鉴权 | `/auth` | 一、个人中心 | 微信登录 / 登出 |
|
||||
| 宠物档案 | `/pet` | 一、宠物档案 | CRUD + 体重历史分页 |
|
||||
| 成长日记 | `/diary` | 二、成长日记 | 时间轴分页(按年/月/类型/宠物筛选) |
|
||||
| 健康医疗 | `/health` | 三、健康医疗本 | 疫苗 / 驱虫 / 就诊 CRUD + 提醒开关 |
|
||||
| 物资管家 | `/inventory` | 四、物资管家 | CRUD + 低库存预警 + 消耗记录 |
|
||||
| 时光机工坊 | `/timemachine` | 五、时光机工坊 | 保存作品元数据(实际处理在客户端) |
|
||||
| 宠物生活信息卡 | `/life-card` | 六、生活信息卡 | 按分类查询 + 详情 |
|
||||
| 意见反馈 | `/feedback` | 一、个人中心 | 提交反馈 |
|
||||
| 提醒调度 | (内部 Scheduled) | 三、提醒功能 | 每天 08:00 扫描即将到期的疫苗/驱虫 |
|
||||
| 文件上传 | `/file` | (通用) | 单文件上传,本地存储,生产可切换 OSS |
|
||||
|
||||
> **未实现功能(明确排除)**:用户间互动、私信、评论点赞、UGC 地图标注、电商/支付、AI 医疗诊断 —— 详见 [功能设计.md § 附录](../功能设计.md)。
|
||||
|
||||
## 五、快速开始
|
||||
|
||||
### 1. 环境准备
|
||||
|
||||
- JDK 21+
|
||||
- Maven 3.9+
|
||||
- MySQL 8.x
|
||||
- Redis 7.x(可选,登录态需要)
|
||||
|
||||
### 2. 初始化数据库
|
||||
|
||||
```bash
|
||||
mysql -u root -p < src/main/resources/sql/pawtrace.sql
|
||||
```
|
||||
|
||||
### 3. 修改配置
|
||||
|
||||
编辑 `application-dev.yml`,修改 MySQL/Redis 账号密码及 `application.yml` 中的微信小程序 `appid/secret`、OSS 配置。
|
||||
|
||||
### 4. 启动
|
||||
|
||||
```bash
|
||||
# 方式一:IDE 直接运行 ServerPawtraceApplication
|
||||
# 方式二:命令行
|
||||
mvn spring-boot:run
|
||||
# 方式三:打包运行
|
||||
mvn clean package -DskipTests
|
||||
java -jar target/server-pawtrace-0.0.1.jar
|
||||
```
|
||||
|
||||
启动成功后:
|
||||
|
||||
- 接口地址: <http://localhost:8080/api>
|
||||
- 接口文档: <http://localhost:8080/api/doc.html>
|
||||
|
||||
## 六、核心约定
|
||||
|
||||
### 1. 统一返回
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "操作成功",
|
||||
"data": { ... },
|
||||
"timestamp": 1718000000000
|
||||
}
|
||||
```
|
||||
|
||||
业务码详见 [ResultCode.java](src/main/java/com/pawtrace/server/common/constant/ResultCode.java)。
|
||||
|
||||
### 2. 鉴权
|
||||
|
||||
使用 Sa-Token,前端在请求头携带 `Authorization: <token>`(由 `/auth/login` 返回)。除以下白名单外,所有接口需要登录:
|
||||
|
||||
```
|
||||
/auth/login /auth/logout /file/**
|
||||
/doc.html /swagger-ui.html /v3/api-docs/**
|
||||
```
|
||||
|
||||
### 3. 业务约束
|
||||
|
||||
- 宠物上限:`MAX_PET_COUNT = 10`
|
||||
- 单条日记图片上限:`MAX_DIARY_IMAGES = 9`
|
||||
- 时光机视频图片上限:`MAX_TIMEMACHINE_IMAGES = 15`
|
||||
- 提醒提前天数:支持 `1` / `3` / `7` 天
|
||||
|
||||
### 4. 数据隔离
|
||||
|
||||
所有业务表都带 `user_id` 字段,Service 层强制按当前登录用户过滤,严禁出现跨用户读取。
|
||||
|
||||
## 七、扩展指引
|
||||
|
||||
| 想要做的事 | 修改点 |
|
||||
|------------------|----------------------------------------------------------------------------|
|
||||
| 切换到 OSS | 实现 `OssService` 接口,替换 `FileController` 中的 `file.transferTo(...)` |
|
||||
| 新增宠物种类 | 扩展 `Pet.species` 枚举值与 `PetGenderEnum` 等枚举 |
|
||||
| 新增日记类型 | 扩展 `DiaryTypeEnum` |
|
||||
| 接入实际的微信订阅消息推送 | `ReminderScheduledTask#scanUpcomingReminders` 的 `TODO` 位置,调用 `wx-java` 推送 |
|
||||
| 接入管理端 | 现有接口已经支持按 userId 过滤,管理端只需另写一个 `ROLE_ADMIN` 路由 + 后台 `Controller` |
|
||||
| 关闭/开启 WebSocket | `pom.xml` 已预留 `spring-boot-starter-websocket`,业务需要时编写 `WebSocket` Handler |
|
||||
|
||||
## 八、版本规划
|
||||
|
||||
- **v0.0.1(当前)**:项目骨架 + 基础 CRUD + 鉴权 + 提醒任务占位
|
||||
- **v0.1.0**:微信登录接入、订阅消息推送、文件存储切换 OSS
|
||||
- **v0.2.0**:管理端接口、统计接口、数据备份/恢复
|
||||
- **v1.0.0**:多端同步、家庭共享(可选)
|
||||
|
||||
## 九、许可证
|
||||
|
||||
MIT License. 详见 [LICENSE](../LICENSE)(如有)。
|
||||
|
||||
## 十、贡献
|
||||
|
||||
1. Fork 本仓库
|
||||
2. 在 `feature/xxx` 分支开发
|
||||
3. 提交 PR 前请保证 `mvn clean compile` 通过
|
||||
4. 提交信息请遵循 `feat: ...` / `fix: ...` / `docs: ...` 规范
|
||||
|
||||
---
|
||||
|
||||
> Made with ❤️ by PawTrace Team
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>admin-pawtrace</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
{
|
||||
"name": "admin-pawtrace",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vue-tsc -b && vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"vue": "^3.5.34",
|
||||
"axios": "^1.17.0",
|
||||
"pinia": "^3.0.4",
|
||||
"vue-router": "^5.1.0",
|
||||
"ant-design-vue": "~4.2.6",
|
||||
"@ant-design/icons-vue": "^7.0.1",
|
||||
"@vueuse/core": "^14.3.0",
|
||||
"@wangeditor/editor": "^5.1.23",
|
||||
"@wangeditor/editor-for-vue": "^1.0.2",
|
||||
"echarts": "^6.1.0",
|
||||
"nprogress": "^0.2.0",
|
||||
"pinia-plugin-persistedstate": "^4.7.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^24.12.3",
|
||||
"@vitejs/plugin-vue": "^6.0.6",
|
||||
"@vue/tsconfig": "^0.9.1",
|
||||
"typescript": "~6.0.2",
|
||||
"vite": "^8.0.12",
|
||||
"vue-tsc": "^3.2.8",
|
||||
"@iconify/vue": "^5.0.1",
|
||||
"sass": "^1.101.0",
|
||||
"unplugin-auto-import": "^21.0.0",
|
||||
"unplugin-vue-components": "^32.1.0",
|
||||
"vite-plugin-vue-devtools": "^8.1.2"
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 9.3 KiB |
|
|
@ -0,0 +1,24 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg">
|
||||
<symbol id="bluesky-icon" viewBox="0 0 16 17">
|
||||
<g clip-path="url(#bluesky-clip)"><path fill="#08060d" d="M7.75 7.735c-.693-1.348-2.58-3.86-4.334-5.097-1.68-1.187-2.32-.981-2.74-.79C.188 2.065.1 2.812.1 3.251s.241 3.602.398 4.13c.52 1.744 2.367 2.333 4.07 2.145-2.495.37-4.71 1.278-1.805 4.512 3.196 3.309 4.38-.71 4.987-2.746.608 2.036 1.307 5.91 4.93 2.746 2.72-2.746.747-4.143-1.747-4.512 1.702.189 3.55-.4 4.07-2.145.156-.528.397-3.691.397-4.13s-.088-1.186-.575-1.406c-.42-.19-1.06-.395-2.741.79-1.755 1.24-3.64 3.752-4.334 5.099"/></g>
|
||||
<defs><clipPath id="bluesky-clip"><path fill="#fff" d="M.1.85h15.3v15.3H.1z"/></clipPath></defs>
|
||||
</symbol>
|
||||
<symbol id="discord-icon" viewBox="0 0 20 19">
|
||||
<path fill="#08060d" d="M16.224 3.768a14.5 14.5 0 0 0-3.67-1.153c-.158.286-.343.67-.47.976a13.5 13.5 0 0 0-4.067 0c-.128-.306-.317-.69-.476-.976A14.4 14.4 0 0 0 3.868 3.77C1.546 7.28.916 10.703 1.231 14.077a14.7 14.7 0 0 0 4.5 2.306q.545-.748.965-1.587a9.5 9.5 0 0 1-1.518-.74q.191-.14.372-.293c2.927 1.369 6.107 1.369 8.999 0q.183.152.372.294-.723.437-1.52.74.418.838.963 1.588a14.6 14.6 0 0 0 4.504-2.308c.37-3.911-.63-7.302-2.644-10.309m-9.13 8.234c-.878 0-1.599-.82-1.599-1.82 0-.998.705-1.82 1.6-1.82.894 0 1.614.82 1.599 1.82.001 1-.705 1.82-1.6 1.82m5.91 0c-.878 0-1.599-.82-1.599-1.82 0-.998.705-1.82 1.6-1.82.893 0 1.614.82 1.599 1.82 0 1-.706 1.82-1.6 1.82"/>
|
||||
</symbol>
|
||||
<symbol id="documentation-icon" viewBox="0 0 21 20">
|
||||
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="m15.5 13.333 1.533 1.322c.645.555.967.833.967 1.178s-.322.623-.967 1.179L15.5 18.333m-3.333-5-1.534 1.322c-.644.555-.966.833-.966 1.178s.322.623.966 1.179l1.534 1.321"/>
|
||||
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M17.167 10.836v-4.32c0-1.41 0-2.117-.224-2.68-.359-.906-1.118-1.621-2.08-1.96-.599-.21-1.349-.21-2.848-.21-2.623 0-3.935 0-4.983.369-1.684.591-3.013 1.842-3.641 3.428C3 6.449 3 7.684 3 10.154v2.122c0 2.558 0 3.838.706 4.726q.306.383.713.671c.76.536 1.79.64 3.581.66"/>
|
||||
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M3 10a2.78 2.78 0 0 1 2.778-2.778c.555 0 1.209.097 1.748-.047.48-.129.854-.503.982-.982.145-.54.048-1.194.048-1.749a2.78 2.78 0 0 1 2.777-2.777"/>
|
||||
</symbol>
|
||||
<symbol id="github-icon" viewBox="0 0 19 19">
|
||||
<path fill="#08060d" fill-rule="evenodd" d="M9.356 1.85C5.05 1.85 1.57 5.356 1.57 9.694a7.84 7.84 0 0 0 5.324 7.44c.387.079.528-.168.528-.376 0-.182-.013-.805-.013-1.454-2.165.467-2.616-.935-2.616-.935-.349-.91-.864-1.143-.864-1.143-.71-.48.051-.48.051-.48.787.051 1.2.805 1.2.805.695 1.194 1.817.857 2.268.649.064-.507.27-.857.49-1.052-1.728-.182-3.545-.857-3.545-3.87 0-.857.31-1.558.8-2.104-.078-.195-.349-1 .077-2.078 0 0 .657-.208 2.14.805a7.5 7.5 0 0 1 1.946-.26c.657 0 1.328.092 1.946.26 1.483-1.013 2.14-.805 2.14-.805.426 1.078.155 1.883.078 2.078.502.546.799 1.247.799 2.104 0 3.013-1.818 3.675-3.558 3.87.284.247.528.714.528 1.454 0 1.052-.012 1.896-.012 2.156 0 .208.142.455.528.377a7.84 7.84 0 0 0 5.324-7.441c.013-4.338-3.48-7.844-7.773-7.844" clip-rule="evenodd"/>
|
||||
</symbol>
|
||||
<symbol id="social-icon" viewBox="0 0 20 20">
|
||||
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M12.5 6.667a4.167 4.167 0 1 0-8.334 0 4.167 4.167 0 0 0 8.334 0"/>
|
||||
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M2.5 16.667a5.833 5.833 0 0 1 8.75-5.053m3.837.474.513 1.035c.07.144.257.282.414.309l.93.155c.596.1.736.536.307.965l-.723.73a.64.64 0 0 0-.152.531l.207.903c.164.715-.213.991-.84.618l-.872-.52a.63.63 0 0 0-.577 0l-.872.52c-.624.373-1.003.094-.84-.618l.207-.903a.64.64 0 0 0-.152-.532l-.723-.729c-.426-.43-.289-.864.306-.964l.93-.156a.64.64 0 0 0 .412-.31l.513-1.034c.28-.562.735-.562 1.012 0"/>
|
||||
</symbol>
|
||||
<symbol id="x-icon" viewBox="0 0 19 19">
|
||||
<path fill="#08060d" fill-rule="evenodd" d="M1.893 1.98c.052.072 1.245 1.769 2.653 3.77l2.892 4.114c.183.261.333.48.333.486s-.068.089-.152.183l-.522.593-.765.867-3.597 4.087c-.375.426-.734.834-.798.905a1 1 0 0 0-.118.148c0 .01.236.017.664.017h.663l.729-.83c.4-.457.796-.906.879-.999a692 692 0 0 0 1.794-2.038c.034-.037.301-.34.594-.675l.551-.624.345-.392a7 7 0 0 1 .34-.374c.006 0 .93 1.306 2.052 2.903l2.084 2.965.045.063h2.275c1.87 0 2.273-.003 2.266-.021-.008-.02-1.098-1.572-3.894-5.547-2.013-2.862-2.28-3.246-2.273-3.266.008-.019.282-.332 2.085-2.38l2-2.274 1.567-1.782c.022-.028-.016-.03-.65-.03h-.674l-.3.342a871 871 0 0 1-1.782 2.025c-.067.075-.405.458-.75.852a100 100 0 0 1-.803.91c-.148.172-.299.344-.99 1.127-.304.343-.32.358-.345.327-.015-.019-.904-1.282-1.976-2.808L6.365 1.85H1.8zm1.782.91 8.078 11.294c.772 1.08 1.413 1.973 1.425 1.984.016.017.241.02 1.05.017l1.03-.004-2.694-3.766L7.796 5.75 5.722 2.852l-1.039-.004-1.039-.004z" clip-rule="evenodd"/>
|
||||
</symbol>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.9 KiB |
|
|
@ -0,0 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
import HelloWorld from './components/HelloWorld.vue'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<HelloWorld />
|
||||
</template>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 8.5 KiB |
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 496 B |
|
|
@ -0,0 +1,95 @@
|
|||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import viteLogo from '../assets/vite.svg'
|
||||
import heroImg from '../assets/hero.png'
|
||||
import vueLogo from '../assets/vue.svg'
|
||||
|
||||
const count = ref(0)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section id="center">
|
||||
<div class="hero">
|
||||
<img :src="heroImg" class="base" width="170" height="179" alt="" />
|
||||
<img :src="vueLogo" class="framework" alt="Vue logo" />
|
||||
<img :src="viteLogo" class="vite" alt="Vite logo" />
|
||||
</div>
|
||||
<div>
|
||||
<h1>Get started</h1>
|
||||
<p>Edit <code>src/App.vue</code> and save to test <code>HMR</code></p>
|
||||
</div>
|
||||
<button type="button" class="counter" @click="count++">
|
||||
Count is {{ count }}
|
||||
</button>
|
||||
</section>
|
||||
|
||||
<div class="ticks"></div>
|
||||
|
||||
<section id="next-steps">
|
||||
<div id="docs">
|
||||
<svg class="icon" role="presentation" aria-hidden="true">
|
||||
<use href="/icons.svg#documentation-icon"></use>
|
||||
</svg>
|
||||
<h2>Documentation</h2>
|
||||
<p>Your questions, answered</p>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="https://vite.dev/" target="_blank">
|
||||
<img class="logo" :src="viteLogo" alt="" />
|
||||
Explore Vite
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://vuejs.org/" target="_blank">
|
||||
<img class="button-icon" :src="vueLogo" alt="" />
|
||||
Learn more
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div id="social">
|
||||
<svg class="icon" role="presentation" aria-hidden="true">
|
||||
<use href="/icons.svg#social-icon"></use>
|
||||
</svg>
|
||||
<h2>Connect with us</h2>
|
||||
<p>Join the Vite community</p>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="https://github.com/vitejs/vite" target="_blank">
|
||||
<svg class="button-icon" role="presentation" aria-hidden="true">
|
||||
<use href="/icons.svg#github-icon"></use>
|
||||
</svg>
|
||||
GitHub
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://chat.vite.dev/" target="_blank">
|
||||
<svg class="button-icon" role="presentation" aria-hidden="true">
|
||||
<use href="/icons.svg#discord-icon"></use>
|
||||
</svg>
|
||||
Discord
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://x.com/vite_js" target="_blank">
|
||||
<svg class="button-icon" role="presentation" aria-hidden="true">
|
||||
<use href="/icons.svg#x-icon"></use>
|
||||
</svg>
|
||||
X.com
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://bsky.app/profile/vite.dev" target="_blank">
|
||||
<svg class="button-icon" role="presentation" aria-hidden="true">
|
||||
<use href="/icons.svg#bluesky-icon"></use>
|
||||
</svg>
|
||||
Bluesky
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="ticks"></div>
|
||||
<section id="spacer"></section>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
import { createApp } from 'vue'
|
||||
import './style.css'
|
||||
import App from './App.vue'
|
||||
|
||||
createApp(App).mount('#app')
|
||||
|
|
@ -0,0 +1,296 @@
|
|||
:root {
|
||||
--text: #6b6375;
|
||||
--text-h: #08060d;
|
||||
--bg: #fff;
|
||||
--border: #e5e4e7;
|
||||
--code-bg: #f4f3ec;
|
||||
--accent: #aa3bff;
|
||||
--accent-bg: rgba(170, 59, 255, 0.1);
|
||||
--accent-border: rgba(170, 59, 255, 0.5);
|
||||
--social-bg: rgba(244, 243, 236, 0.5);
|
||||
--shadow:
|
||||
rgba(0, 0, 0, 0.1) 0 10px 15px -3px, rgba(0, 0, 0, 0.05) 0 4px 6px -2px;
|
||||
|
||||
--sans: system-ui, 'Segoe UI', Roboto, sans-serif;
|
||||
--heading: system-ui, 'Segoe UI', Roboto, sans-serif;
|
||||
--mono: ui-monospace, Consolas, monospace;
|
||||
|
||||
font: 18px/145% var(--sans);
|
||||
letter-spacing: 0.18px;
|
||||
color-scheme: light dark;
|
||||
color: var(--text);
|
||||
background: var(--bg);
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--text: #9ca3af;
|
||||
--text-h: #f3f4f6;
|
||||
--bg: #16171d;
|
||||
--border: #2e303a;
|
||||
--code-bg: #1f2028;
|
||||
--accent: #c084fc;
|
||||
--accent-bg: rgba(192, 132, 252, 0.15);
|
||||
--accent-border: rgba(192, 132, 252, 0.5);
|
||||
--social-bg: rgba(47, 48, 58, 0.5);
|
||||
--shadow:
|
||||
rgba(0, 0, 0, 0.4) 0 10px 15px -3px, rgba(0, 0, 0, 0.25) 0 4px 6px -2px;
|
||||
}
|
||||
|
||||
#social .button-icon {
|
||||
filter: invert(1) brightness(2);
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2 {
|
||||
font-family: var(--heading);
|
||||
font-weight: 500;
|
||||
color: var(--text-h);
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 56px;
|
||||
letter-spacing: -1.68px;
|
||||
margin: 32px 0;
|
||||
@media (max-width: 1024px) {
|
||||
font-size: 36px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
}
|
||||
h2 {
|
||||
font-size: 24px;
|
||||
line-height: 118%;
|
||||
letter-spacing: -0.24px;
|
||||
margin: 0 0 8px;
|
||||
@media (max-width: 1024px) {
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
code,
|
||||
.counter {
|
||||
font-family: var(--mono);
|
||||
display: inline-flex;
|
||||
border-radius: 4px;
|
||||
color: var(--text-h);
|
||||
}
|
||||
|
||||
code {
|
||||
font-size: 15px;
|
||||
line-height: 135%;
|
||||
padding: 4px 8px;
|
||||
background: var(--code-bg);
|
||||
}
|
||||
|
||||
.counter {
|
||||
font-size: 16px;
|
||||
padding: 5px 10px;
|
||||
border-radius: 5px;
|
||||
color: var(--accent);
|
||||
background: var(--accent-bg);
|
||||
border: 2px solid transparent;
|
||||
transition: border-color 0.3s;
|
||||
margin-bottom: 24px;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--accent-border);
|
||||
}
|
||||
&:focus-visible {
|
||||
outline: 2px solid var(--accent);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.hero {
|
||||
position: relative;
|
||||
|
||||
.base,
|
||||
.framework,
|
||||
.vite {
|
||||
inset-inline: 0;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.base {
|
||||
width: 170px;
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.framework,
|
||||
.vite {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.framework {
|
||||
z-index: 1;
|
||||
top: 34px;
|
||||
height: 28px;
|
||||
transform: perspective(2000px) rotateZ(300deg) rotateX(44deg) rotateY(39deg)
|
||||
scale(1.4);
|
||||
}
|
||||
|
||||
.vite {
|
||||
z-index: 0;
|
||||
top: 107px;
|
||||
height: 26px;
|
||||
width: auto;
|
||||
transform: perspective(2000px) rotateZ(300deg) rotateX(40deg) rotateY(39deg)
|
||||
scale(0.8);
|
||||
}
|
||||
}
|
||||
|
||||
#app {
|
||||
width: 1126px;
|
||||
max-width: 100%;
|
||||
margin: 0 auto;
|
||||
text-align: center;
|
||||
border-inline: 1px solid var(--border);
|
||||
min-height: 100svh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
#center {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 25px;
|
||||
place-content: center;
|
||||
place-items: center;
|
||||
flex-grow: 1;
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
padding: 32px 20px 24px;
|
||||
gap: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
#next-steps {
|
||||
display: flex;
|
||||
border-top: 1px solid var(--border);
|
||||
text-align: left;
|
||||
|
||||
& > div {
|
||||
flex: 1 1 0;
|
||||
padding: 32px;
|
||||
@media (max-width: 1024px) {
|
||||
padding: 24px 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
margin-bottom: 16px;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
#docs {
|
||||
border-right: 1px solid var(--border);
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
border-right: none;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
}
|
||||
|
||||
#next-steps ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin: 32px 0 0;
|
||||
|
||||
.logo {
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--text-h);
|
||||
font-size: 16px;
|
||||
border-radius: 6px;
|
||||
background: var(--social-bg);
|
||||
display: flex;
|
||||
padding: 6px 12px;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
text-decoration: none;
|
||||
transition: box-shadow 0.3s;
|
||||
|
||||
&:hover {
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
.button-icon {
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
margin-top: 20px;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
|
||||
li {
|
||||
flex: 1 1 calc(50% - 8px);
|
||||
}
|
||||
|
||||
a {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#spacer {
|
||||
height: 88px;
|
||||
border-top: 1px solid var(--border);
|
||||
@media (max-width: 1024px) {
|
||||
height: 48px;
|
||||
}
|
||||
}
|
||||
|
||||
.ticks {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
|
||||
&::before,
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -4.5px;
|
||||
border: 5px solid transparent;
|
||||
}
|
||||
|
||||
&::before {
|
||||
left: 0;
|
||||
border-left-color: var(--border);
|
||||
}
|
||||
&::after {
|
||||
right: 0;
|
||||
border-right-color: var(--border);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||
"types": ["vite/client"],
|
||||
|
||||
/* Linting */
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"erasableSyntaxOnly": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"]
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"files": [],
|
||||
"references": [
|
||||
{ "path": "./tsconfig.app.json" },
|
||||
{ "path": "./tsconfig.node.json" }
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||
"target": "es2023",
|
||||
"lib": ["ES2023"],
|
||||
"module": "esnext",
|
||||
"types": ["node"],
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
|
||||
/* Linting */
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"erasableSyntaxOnly": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [vue()],
|
||||
})
|
||||
|
|
@ -0,0 +1,249 @@
|
|||
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.pawtrace</groupId>
|
||||
<artifactId>server-pawtrace</artifactId>
|
||||
<version>0.0.1</version>
|
||||
<name>server-pawtrace</name>
|
||||
<description>宠迹服务端 - 宠物成长记录与生活管理</description>
|
||||
|
||||
<properties>
|
||||
<java.version>21</java.version>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||
<spring-boot.version>4.0.6</spring-boot.version>
|
||||
<mybatis-plus.version>3.5.15</mybatis-plus.version>
|
||||
<hutool.version>5.8.44</hutool.version>
|
||||
<sa-token.version>1.45.0</sa-token.version>
|
||||
<knife4j.version>4.6.0</knife4j.version>
|
||||
<fastjson2.version>2.0.62</fastjson2.version>
|
||||
<fastjson2-extension.version>2.0.61</fastjson2-extension.version>
|
||||
<qiniu.version>7.19.0</qiniu.version>
|
||||
<s3.version>2.44.4</s3.version>
|
||||
<minio.version>9.0.0</minio.version>
|
||||
<aliyun-oss.version>3.18.5</aliyun-oss.version>
|
||||
<tencent-cos.version>5.6.269</tencent-cos.version>
|
||||
<weixin-java.version>4.8.3.B</weixin-java.version>
|
||||
<mapstruct.version>1.6.3</mapstruct.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-websocket</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-redis</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-crypto</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-spring-boot4-starter</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-jsqlparser-4.9</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.mysql</groupId>
|
||||
<artifactId>mysql-connector-j</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-all</artifactId>
|
||||
<version>${hutool.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-spring-boot4-starter</artifactId>
|
||||
<version>${sa-token.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.github.xingfudeshi</groupId>
|
||||
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
|
||||
<version>${knife4j.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.alibaba.fastjson2</groupId>
|
||||
<artifactId>fastjson2</artifactId>
|
||||
<version>${fastjson2.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.fastjson2</groupId>
|
||||
<artifactId>fastjson2-extension-spring6</artifactId>
|
||||
<version>${fastjson2-extension.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.qiniu</groupId>
|
||||
<artifactId>qiniu-java-sdk</artifactId>
|
||||
<version>${qiniu.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>software.amazon.awssdk</groupId>
|
||||
<artifactId>s3</artifactId>
|
||||
<version>${s3.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.minio</groupId>
|
||||
<artifactId>minio</artifactId>
|
||||
<version>${minio.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.aliyun.oss</groupId>
|
||||
<artifactId>aliyun-sdk-oss</artifactId>
|
||||
<version>${aliyun-oss.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.qcloud</groupId>
|
||||
<artifactId>cos_api</artifactId>
|
||||
<version>${tencent-cos.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.github.binarywang</groupId>
|
||||
<artifactId>wx-java-miniapp-spring-boot-starter</artifactId>
|
||||
<version>${weixin-java.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.mapstruct</groupId>
|
||||
<artifactId>mapstruct</artifactId>
|
||||
<version>${mapstruct.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-dependencies</artifactId>
|
||||
<version>${spring-boot.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-bom</artifactId>
|
||||
<version>${mybatis-plus.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<build>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
</resource>
|
||||
<resource>
|
||||
<directory>src/main/java</directory>
|
||||
<includes>
|
||||
<include>**/mapper/*.xml</include>
|
||||
</includes>
|
||||
</resource>
|
||||
</resources>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.1</version>
|
||||
<configuration>
|
||||
<source>21</source>
|
||||
<target>21</target>
|
||||
<encoding>UTF-8</encoding>
|
||||
<compilerArgs>
|
||||
<arg>-parameters</arg>
|
||||
</compilerArgs>
|
||||
<annotationProcessorPaths>
|
||||
<path>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
</path>
|
||||
<path>
|
||||
<groupId>org.mapstruct</groupId>
|
||||
<artifactId>mapstruct-processor</artifactId>
|
||||
<version>${mapstruct.version}</version>
|
||||
</path>
|
||||
</annotationProcessorPaths>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<version>${spring-boot.version}</version>
|
||||
<configuration>
|
||||
<mainClass>com.pawtrace.server.ServerPawtraceApplication</mainClass>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>repackage</id>
|
||||
<goals>
|
||||
<goal>repackage</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>huaweicloud</id>
|
||||
<name>huawei</name>
|
||||
<url>https://mirrors.huaweicloud.com/repository/maven/</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>aliyunmaven</id>
|
||||
<name>aliyun</name>
|
||||
<url>https://maven.aliyun.com/repository/public</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>spring-milestones</id>
|
||||
<name>Spring Milestones</name>
|
||||
<url>https://repo.spring.io/milestone</url>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>spring-snapshots</id>
|
||||
<name>Spring Snapshots</name>
|
||||
<url>https://repo.spring.io/snapshot</url>
|
||||
<releases>
|
||||
<enabled>false</enabled>
|
||||
</releases>
|
||||
</repository>
|
||||
</repositories>
|
||||
</project>
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package com.pawtrace.server;
|
||||
|
||||
import org.mybatis.spring.annotation.MapperScan;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.scheduling.annotation.EnableAsync;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
|
||||
/**
|
||||
* 宠迹服务端启动类
|
||||
*
|
||||
* @author PawTrace Team
|
||||
*/
|
||||
@EnableAsync
|
||||
@EnableScheduling
|
||||
@SpringBootApplication
|
||||
@MapperScan("com.pawtrace.server.mapper")
|
||||
public class ServerPawtraceApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(ServerPawtraceApplication.class, args);
|
||||
System.out.println("""
|
||||
|
||||
====================================================
|
||||
宠迹 PawTrace 服务端启动成功
|
||||
Knife4j: http://localhost:8080/api/doc.html
|
||||
====================================================
|
||||
""");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
package com.pawtrace.server.common.constant;
|
||||
|
||||
/**
|
||||
* 通用常量
|
||||
*
|
||||
* @author PawTrace Team
|
||||
*/
|
||||
public final class CommonConstants {
|
||||
|
||||
private CommonConstants() {
|
||||
}
|
||||
|
||||
/** 通用标识 */
|
||||
public static final String DEFAULT = "default";
|
||||
public static final String ROOT = "0";
|
||||
public static final Long ROOT_ID = 0L;
|
||||
|
||||
/** 逻辑删除标识 */
|
||||
public static final Integer DELETED = 1;
|
||||
public static final Integer NOT_DELETED = 0;
|
||||
|
||||
/** 状态:启用/禁用 */
|
||||
public static final Integer STATUS_ENABLED = 1;
|
||||
public static final Integer STATUS_DISABLED = 0;
|
||||
|
||||
/** 是/否 */
|
||||
public static final Integer YES = 1;
|
||||
public static final Integer NO = 0;
|
||||
|
||||
/** 分页默认值 */
|
||||
public static final Long DEFAULT_PAGE_NUM = 1L;
|
||||
public static final Long DEFAULT_PAGE_SIZE = 10L;
|
||||
public static final Long MAX_PAGE_SIZE = 200L;
|
||||
|
||||
/** 业务限制 */
|
||||
public static final Integer MAX_PET_COUNT = 10;
|
||||
public static final Integer MAX_DIARY_IMAGES = 9;
|
||||
public static final Integer MAX_TIMEMACHINE_IMAGES = 15;
|
||||
|
||||
/** Header */
|
||||
public static final String HEADER_AUTHORIZATION = "Authorization";
|
||||
public static final String HEADER_USER_ID = "X-User-Id";
|
||||
|
||||
/** 角色标识 */
|
||||
public static final String ROLE_USER = "user";
|
||||
public static final String ROLE_ADMIN = "admin";
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
package com.pawtrace.server.common.constant;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 业务返回码枚举
|
||||
*
|
||||
* @author PawTrace Team
|
||||
*/
|
||||
@Getter
|
||||
public enum ResultCode {
|
||||
|
||||
SUCCESS(200, "操作成功"),
|
||||
FAIL(500, "操作失败"),
|
||||
|
||||
BAD_REQUEST(400, "请求参数错误"),
|
||||
UNAUTHORIZED(401, "未登录或登录已过期"),
|
||||
FORBIDDEN(403, "无权访问"),
|
||||
NOT_FOUND(404, "资源不存在"),
|
||||
METHOD_NOT_ALLOWED(405, "请求方法不允许"),
|
||||
|
||||
// 业务相关 1xxx
|
||||
USER_NOT_FOUND(1001, "用户不存在"),
|
||||
USER_ALREADY_EXISTS(1002, "用户已存在"),
|
||||
LOGIN_FAILED(1003, "登录失败"),
|
||||
TOKEN_INVALID(1004, "令牌无效"),
|
||||
|
||||
PET_NOT_FOUND(2001, "宠物不存在"),
|
||||
PET_LIMIT_EXCEEDED(2002, "宠物数量已达上限"),
|
||||
|
||||
DIARY_NOT_FOUND(3001, "日记不存在"),
|
||||
|
||||
HEALTH_RECORD_NOT_FOUND(4001, "健康记录不存在"),
|
||||
|
||||
INVENTORY_NOT_FOUND(5001, "物资不存在"),
|
||||
|
||||
FILE_UPLOAD_FAILED(9001, "文件上传失败"),
|
||||
FILE_TYPE_INVALID(9002, "文件类型不支持");
|
||||
|
||||
private final Integer code;
|
||||
private final String message;
|
||||
|
||||
ResultCode(Integer code, String message) {
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package com.pawtrace.server.common.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 日记类型
|
||||
*
|
||||
* @author PawTrace Team
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum DiaryTypeEnum {
|
||||
|
||||
DAILY(1, "日常"),
|
||||
GROWTH(2, "成长"),
|
||||
MEDICAL(3, "医疗"),
|
||||
DIET(4, "饮食"),
|
||||
FUN(5, "趣事");
|
||||
|
||||
private final Integer code;
|
||||
private final String desc;
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package com.pawtrace.server.common.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 生活信息卡分类
|
||||
*
|
||||
* @author PawTrace Team
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum LifeCardCategoryEnum {
|
||||
|
||||
HOSPITAL(1, "宠物医院"),
|
||||
RESTAURANT(2, "宠物友好餐厅"),
|
||||
PARK(3, "宠物公园"),
|
||||
STORE(4, "宠物店"),
|
||||
GROOMING(5, "宠物美容");
|
||||
|
||||
private final Integer code;
|
||||
private final String desc;
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package com.pawtrace.server.common.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 宠物性别
|
||||
*
|
||||
* @author PawTrace Team
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum PetGenderEnum {
|
||||
|
||||
UNKNOWN(0, "未知"),
|
||||
MALE(1, "公"),
|
||||
FEMALE(2, "母");
|
||||
|
||||
private final Integer code;
|
||||
private final String desc;
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
package com.pawtrace.server.common.exception;
|
||||
|
||||
import com.pawtrace.server.common.constant.ResultCode;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.io.Serial;
|
||||
|
||||
/**
|
||||
* 业务异常
|
||||
*
|
||||
* @author PawTrace Team
|
||||
*/
|
||||
@Getter
|
||||
public class BusinessException extends RuntimeException {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 业务错误码 */
|
||||
private final Integer code;
|
||||
|
||||
public BusinessException(String message) {
|
||||
super(message);
|
||||
this.code = ResultCode.FAIL.getCode();
|
||||
}
|
||||
|
||||
public BusinessException(Integer code, String message) {
|
||||
super(message);
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public BusinessException(ResultCode resultCode) {
|
||||
super(resultCode.getMessage());
|
||||
this.code = resultCode.getCode();
|
||||
}
|
||||
|
||||
public BusinessException(ResultCode resultCode, String message) {
|
||||
super(message);
|
||||
this.code = resultCode.getCode();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
package com.pawtrace.server.common.exception;
|
||||
|
||||
import com.pawtrace.server.common.result.Result;
|
||||
import com.pawtrace.server.common.constant.ResultCode;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.validation.BindException;
|
||||
import org.springframework.validation.FieldError;
|
||||
import org.springframework.web.HttpRequestMethodNotSupportedException;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.MissingServletRequestParameterException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 全局异常处理器
|
||||
*
|
||||
* @author PawTrace Team
|
||||
*/
|
||||
@Slf4j
|
||||
@RestControllerAdvice
|
||||
public class GlobalExceptionHandler {
|
||||
|
||||
@ExceptionHandler(BusinessException.class)
|
||||
public Result<Void> handleBusinessException(BusinessException e, HttpServletRequest request) {
|
||||
log.warn("业务异常 [{}] {} -> {}", e.getCode(), request.getRequestURI(), e.getMessage());
|
||||
return Result.fail(e.getCode(), e.getMessage());
|
||||
}
|
||||
|
||||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||
public Result<Void> handleValidException(MethodArgumentNotValidException e) {
|
||||
String msg = e.getBindingResult().getFieldErrors().stream()
|
||||
.map(FieldError::getDefaultMessage)
|
||||
.collect(Collectors.joining("; "));
|
||||
log.warn("参数校验失败: {}", msg);
|
||||
return Result.fail(ResultCode.BAD_REQUEST.getCode(), msg);
|
||||
}
|
||||
|
||||
@ExceptionHandler(BindException.class)
|
||||
public Result<Void> handleBindException(BindException e) {
|
||||
String msg = e.getBindingResult().getFieldErrors().stream()
|
||||
.map(FieldError::getDefaultMessage)
|
||||
.collect(Collectors.joining("; "));
|
||||
return Result.fail(ResultCode.BAD_REQUEST.getCode(), msg);
|
||||
}
|
||||
|
||||
@ExceptionHandler(MissingServletRequestParameterException.class)
|
||||
public Result<Void> handleMissingParam(MissingServletRequestParameterException e) {
|
||||
return Result.fail(ResultCode.BAD_REQUEST.getCode(), "缺少参数: " + e.getParameterName());
|
||||
}
|
||||
|
||||
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
|
||||
public ResponseEntity<Result<Void>> handleMethodNotSupported(HttpRequestMethodNotSupportedException e) {
|
||||
return ResponseEntity.status(HttpStatus.METHOD_NOT_ALLOWED)
|
||||
.body(Result.fail(ResultCode.METHOD_NOT_ALLOWED.getCode(), e.getMessage()));
|
||||
}
|
||||
|
||||
@ExceptionHandler(Exception.class)
|
||||
public Result<Void> handleException(Exception e, HttpServletRequest request) {
|
||||
log.error("系统异常 [{}] {}", request.getRequestURI(), e.getMessage(), e);
|
||||
return Result.fail(ResultCode.FAIL.getCode(), "系统繁忙,请稍后再试");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
package com.pawtrace.server.common.page;
|
||||
|
||||
import com.pawtrace.server.common.constant.CommonConstants;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 分页请求
|
||||
*
|
||||
* @author PawTrace Team
|
||||
*/
|
||||
@Data
|
||||
public class PageRequest implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 当前页 */
|
||||
private Long pageNum = CommonConstants.DEFAULT_PAGE_NUM;
|
||||
|
||||
/** 每页大小 */
|
||||
private Long pageSize = CommonConstants.DEFAULT_PAGE_SIZE;
|
||||
|
||||
/** 排序字段 */
|
||||
private String orderBy;
|
||||
|
||||
/** 排序方式: asc / desc */
|
||||
private String order = "desc";
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
package com.pawtrace.server.common.page;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 分页结果
|
||||
*
|
||||
* @param <T> 数据类型
|
||||
* @author PawTrace Team
|
||||
*/
|
||||
@Data
|
||||
public class PageResult<T> implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 总记录数 */
|
||||
private Long total;
|
||||
|
||||
/** 当前页 */
|
||||
private Long pageNum;
|
||||
|
||||
/** 每页大小 */
|
||||
private Long pageSize;
|
||||
|
||||
/** 总页数 */
|
||||
private Long pages;
|
||||
|
||||
/** 数据列表 */
|
||||
private List<T> records;
|
||||
|
||||
public PageResult() {
|
||||
this.records = Collections.emptyList();
|
||||
}
|
||||
|
||||
public PageResult(Long total, Long pageNum, Long pageSize, List<T> records) {
|
||||
this.total = total;
|
||||
this.pageNum = pageNum;
|
||||
this.pageSize = pageSize;
|
||||
this.pages = (total + pageSize - 1) / pageSize;
|
||||
this.records = records;
|
||||
}
|
||||
|
||||
public static <T> PageResult<T> empty(Long pageNum, Long pageSize) {
|
||||
return new PageResult<>(0L, pageNum, pageSize, Collections.emptyList());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
package com.pawtrace.server.common.result;
|
||||
|
||||
import com.pawtrace.server.common.constant.ResultCode;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 统一返回结果
|
||||
*
|
||||
* @param <T> 数据类型
|
||||
*/
|
||||
@Data
|
||||
public class Result<T> implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 业务状态码 */
|
||||
private Integer code;
|
||||
|
||||
/** 提示信息 */
|
||||
private String message;
|
||||
|
||||
/** 业务数据 */
|
||||
private T data;
|
||||
|
||||
/** 时间戳 */
|
||||
private Long timestamp;
|
||||
|
||||
public Result() {
|
||||
this.timestamp = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public Result(Integer code, String message, T data) {
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
this.data = data;
|
||||
this.timestamp = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public static <T> Result<T> success() {
|
||||
return new Result<>(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMessage(), null);
|
||||
}
|
||||
|
||||
public static <T> Result<T> success(T data) {
|
||||
return new Result<>(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMessage(), data);
|
||||
}
|
||||
|
||||
public static <T> Result<T> success(String message, T data) {
|
||||
return new Result<>(ResultCode.SUCCESS.getCode(), message, data);
|
||||
}
|
||||
|
||||
public static <T> Result<T> fail() {
|
||||
return new Result<>(ResultCode.FAIL.getCode(), ResultCode.FAIL.getMessage(), null);
|
||||
}
|
||||
|
||||
public static <T> Result<T> fail(String message) {
|
||||
return new Result<>(ResultCode.FAIL.getCode(), message, null);
|
||||
}
|
||||
|
||||
public static <T> Result<T> fail(Integer code, String message) {
|
||||
return new Result<>(code, message, null);
|
||||
}
|
||||
|
||||
public static <T> Result<T> fail(ResultCode resultCode) {
|
||||
return new Result<>(resultCode.getCode(), resultCode.getMessage(), null);
|
||||
}
|
||||
|
||||
public boolean isSuccess() {
|
||||
return ResultCode.SUCCESS.getCode().equals(this.code);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
package com.pawtrace.server.common.utils;
|
||||
|
||||
import com.pawtrace.server.common.constant.CommonConstants;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.Period;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
/**
|
||||
* 日期工具
|
||||
*
|
||||
* @author PawTrace Team
|
||||
*/
|
||||
public final class DateUtils {
|
||||
|
||||
private static final DateTimeFormatter DEFAULT_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
private static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
||||
|
||||
private DateUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据生日计算年龄(年/月)
|
||||
*/
|
||||
public static String calculateAge(LocalDate birthday) {
|
||||
if (birthday == null) {
|
||||
return "未知";
|
||||
}
|
||||
LocalDate now = LocalDate.now();
|
||||
if (birthday.isAfter(now)) {
|
||||
return "未出生";
|
||||
}
|
||||
Period period = Period.between(birthday, now);
|
||||
if (period.getYears() <= 0) {
|
||||
return period.getMonths() + "个月";
|
||||
}
|
||||
return period.getYears() + "岁" + (period.getMonths() > 0 ? period.getMonths() + "个月" : "");
|
||||
}
|
||||
|
||||
public static String format(LocalDate date) {
|
||||
return date == null ? CommonConstants.DEFAULT : date.format(DATE_FORMAT);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
package com.pawtrace.server.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.scheduling.annotation.EnableAsync;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
|
||||
/**
|
||||
* 异步任务 / 线程池配置
|
||||
*
|
||||
* @author PawTrace Team
|
||||
*/
|
||||
@Configuration
|
||||
@EnableAsync
|
||||
public class AsyncConfig {
|
||||
|
||||
@Bean("pawtraceTaskExecutor")
|
||||
public Executor pawtraceTaskExecutor() {
|
||||
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||
executor.setCorePoolSize(8);
|
||||
executor.setMaxPoolSize(32);
|
||||
executor.setQueueCapacity(500);
|
||||
executor.setKeepAliveSeconds(60);
|
||||
executor.setThreadNamePrefix("pawtrace-async-");
|
||||
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
|
||||
executor.initialize();
|
||||
return executor;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package com.pawtrace.server.config;
|
||||
|
||||
import io.swagger.v3.oas.models.OpenAPI;
|
||||
import io.swagger.v3.oas.models.info.Contact;
|
||||
import io.swagger.v3.oas.models.info.Info;
|
||||
import io.swagger.v3.oas.models.info.License;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* Knife4j / OpenAPI 配置
|
||||
*
|
||||
* @author PawTrace Team
|
||||
*/
|
||||
@Configuration
|
||||
public class Knife4jConfig {
|
||||
|
||||
@Bean
|
||||
public OpenAPI pawtraceOpenAPI() {
|
||||
return new OpenAPI()
|
||||
.info(new Info()
|
||||
.title("宠迹 PawTrace 服务端 API")
|
||||
.description("宠物成长记录与生活管理后端接口文档")
|
||||
.version("v1.0.0")
|
||||
.contact(new Contact().name("PawTrace Team").email("dev@pawtrace.com"))
|
||||
.license(new License().name("MIT").url("https://opensource.org/licenses/MIT")));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package com.pawtrace.server.config;
|
||||
|
||||
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
|
||||
import org.apache.ibatis.reflection.MetaObject;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* MyBatis Plus 自动填充处理器
|
||||
*
|
||||
* @author PawTrace Team
|
||||
*/
|
||||
@Component
|
||||
public class MybatisMetaObjectHandler implements MetaObjectHandler {
|
||||
|
||||
@Override
|
||||
public void insertFill(MetaObject metaObject) {
|
||||
this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
|
||||
this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
|
||||
this.strictInsertFill(metaObject, "deleted", Integer.class, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateFill(MetaObject metaObject) {
|
||||
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
package com.pawtrace.server.config;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.DbType;
|
||||
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* MyBatis Plus 配置
|
||||
*
|
||||
* @author PawTrace Team
|
||||
*/
|
||||
@Configuration
|
||||
public class MybatisPlusConfig {
|
||||
|
||||
@Bean
|
||||
public MybatisPlusInterceptor mybatisPlusInterceptor() {
|
||||
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
|
||||
// 分页插件
|
||||
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
|
||||
// 乐观锁插件
|
||||
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
|
||||
return interceptor;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
package com.pawtrace.server.config;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
||||
import com.fasterxml.jackson.annotation.PropertyAccessor;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
|
||||
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||
|
||||
/**
|
||||
* Redis 配置
|
||||
*
|
||||
* @author PawTrace Team
|
||||
*/
|
||||
@Configuration
|
||||
public class RedisConfig {
|
||||
|
||||
@Bean
|
||||
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
|
||||
RedisTemplate<String, Object> template = new RedisTemplate<>();
|
||||
template.setConnectionFactory(factory);
|
||||
|
||||
ObjectMapper om = new ObjectMapper();
|
||||
om.registerModule(new JavaTimeModule());
|
||||
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
|
||||
om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
|
||||
|
||||
Jackson2JsonRedisSerializer<Object> jsonSerializer = new Jackson2JsonRedisSerializer<>(om, Object.class);
|
||||
StringRedisSerializer stringSerializer = new StringRedisSerializer();
|
||||
|
||||
template.setKeySerializer(stringSerializer);
|
||||
template.setHashKeySerializer(stringSerializer);
|
||||
template.setValueSerializer(jsonSerializer);
|
||||
template.setHashValueSerializer(jsonSerializer);
|
||||
template.afterPropertiesSet();
|
||||
return template;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
package com.pawtrace.server.config;
|
||||
|
||||
import cn.dev33.satoken.interceptor.SaInterceptor;
|
||||
import cn.dev33.satoken.router.SaRouter;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
/**
|
||||
* Sa-Token 路由拦截配置
|
||||
*
|
||||
* @author PawTrace Team
|
||||
*/
|
||||
@Configuration
|
||||
public class SaTokenConfig implements WebMvcConfigurer {
|
||||
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
registry.addInterceptor(new SaInterceptor())
|
||||
.addPathPatterns("/**")
|
||||
.excludePathPatterns(
|
||||
"/auth/login",
|
||||
"/auth/logout",
|
||||
"/doc.html",
|
||||
"/swagger-ui.html",
|
||||
"/swagger-resources/**",
|
||||
"/webjars/**",
|
||||
"/v3/api-docs/**",
|
||||
"/favicon.ico",
|
||||
"/file/**"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package com.pawtrace.server.config;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.CorsRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
/**
|
||||
* Web 配置(CORS、静态资源等)
|
||||
*
|
||||
* @author PawTrace Team
|
||||
*/
|
||||
@Configuration
|
||||
public class WebConfig implements WebMvcConfigurer {
|
||||
|
||||
@Override
|
||||
public void addCorsMappings(CorsRegistry registry) {
|
||||
registry.addMapping("/**")
|
||||
.allowedOriginPatterns("*")
|
||||
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH")
|
||||
.allowedHeaders("*")
|
||||
.allowCredentials(true)
|
||||
.maxAge(3600);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
package com.pawtrace.server.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 基础实体
|
||||
*
|
||||
* @author PawTrace Team
|
||||
*/
|
||||
@Data
|
||||
public class BaseEntity implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "主键ID")
|
||||
@TableId(value = "id", type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||
@TableField(value = "create_time", fill = FieldFill.INSERT)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
@Schema(description = "更新时间")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||
@TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
@Schema(description = "逻辑删除标识 0未删 1已删")
|
||||
@TableLogic
|
||||
@TableField("deleted")
|
||||
private Integer deleted;
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
package com.pawtrace.server.modules.diary.controller;
|
||||
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import com.pawtrace.server.common.page.PageRequest;
|
||||
import com.pawtrace.server.common.page.PageResult;
|
||||
import com.pawtrace.server.common.result.Result;
|
||||
import com.pawtrace.server.modules.diary.entity.Diary;
|
||||
import com.pawtrace.server.modules.diary.service.DiaryService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* 成长日记 Controller
|
||||
*
|
||||
* @author PawTrace Team
|
||||
*/
|
||||
@Tag(name = "成长日记")
|
||||
@RestController
|
||||
@RequestMapping("/diary")
|
||||
public class DiaryController {
|
||||
|
||||
private final DiaryService diaryService;
|
||||
|
||||
public DiaryController(DiaryService diaryService) {
|
||||
this.diaryService = diaryService;
|
||||
}
|
||||
|
||||
@Operation(summary = "时间轴分页")
|
||||
@GetMapping("/page")
|
||||
public Result<PageResult<Diary>> page(@RequestParam(required = false) Long petId,
|
||||
@RequestParam(required = false) Integer diaryType,
|
||||
@RequestParam(required = false) String yearMonth,
|
||||
PageRequest request) {
|
||||
return Result.success(diaryService.pageTimeline(StpUtil.getLoginIdAsLong(), petId, diaryType, yearMonth, request));
|
||||
}
|
||||
|
||||
@Operation(summary = "新增/更新日记")
|
||||
@PostMapping("/save")
|
||||
public Result<Boolean> save(@RequestBody Diary diary) {
|
||||
diary.setUserId(StpUtil.getLoginIdAsLong());
|
||||
return Result.success(diaryService.saveOrUpdate(diary));
|
||||
}
|
||||
|
||||
@Operation(summary = "删除日记")
|
||||
@DeleteMapping("/{id}")
|
||||
public Result<Boolean> delete(@PathVariable Long id) {
|
||||
return Result.success(diaryService.removeById(id));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
package com.pawtrace.server.modules.diary.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.pawtrace.server.entity.BaseEntity;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.time.LocalDate;
|
||||
|
||||
/**
|
||||
* 成长日记
|
||||
*
|
||||
* @author PawTrace Team
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("pt_diary")
|
||||
@Schema(description = "成长日记")
|
||||
public class Diary extends BaseEntity {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "用户ID")
|
||||
private Long userId;
|
||||
|
||||
@Schema(description = "关联宠物ID")
|
||||
private Long petId;
|
||||
|
||||
@Schema(description = "标题")
|
||||
private String title;
|
||||
|
||||
@Schema(description = "内容")
|
||||
private String content;
|
||||
|
||||
@Schema(description = "媒体URL列表(JSON数组字符串)")
|
||||
private String mediaUrls;
|
||||
|
||||
@Schema(description = "心情标签")
|
||||
private String moodTags;
|
||||
|
||||
@Schema(description = "地点文本")
|
||||
private String location;
|
||||
|
||||
@Schema(description = "记录类型 1日常 2成长 3医疗 4饮食 5趣事")
|
||||
private Integer diaryType;
|
||||
|
||||
@Schema(description = "发生日期")
|
||||
private LocalDate happenDate;
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package com.pawtrace.server.modules.diary.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.pawtrace.server.modules.diary.entity.Diary;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 日记 Mapper
|
||||
*
|
||||
* @author PawTrace Team
|
||||
*/
|
||||
@Mapper
|
||||
public interface DiaryMapper extends BaseMapper<Diary> {
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package com.pawtrace.server.modules.diary.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.pawtrace.server.common.page.PageRequest;
|
||||
import com.pawtrace.server.common.page.PageResult;
|
||||
import com.pawtrace.server.modules.diary.entity.Diary;
|
||||
|
||||
/**
|
||||
* 日记 Service
|
||||
*
|
||||
* @author PawTrace Team
|
||||
*/
|
||||
public interface DiaryService extends IService<Diary> {
|
||||
|
||||
/**
|
||||
* 时间轴分页(支持年份/月份/类型/宠物筛选)
|
||||
*/
|
||||
PageResult<Diary> pageTimeline(Long userId, Long petId, Integer diaryType, String yearMonth, PageRequest request);
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
package com.pawtrace.server.modules.diary.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.pawtrace.server.common.page.PageRequest;
|
||||
import com.pawtrace.server.common.page.PageResult;
|
||||
import com.pawtrace.server.modules.diary.entity.Diary;
|
||||
import com.pawtrace.server.modules.diary.mapper.DiaryMapper;
|
||||
import com.pawtrace.server.modules.diary.service.DiaryService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* 日记 Service 实现
|
||||
*
|
||||
* @author PawTrace Team
|
||||
*/
|
||||
@Service
|
||||
public class DiaryServiceImpl extends ServiceImpl<DiaryMapper, Diary> implements DiaryService {
|
||||
|
||||
@Override
|
||||
public PageResult<Diary> pageTimeline(Long userId, Long petId, Integer diaryType, String yearMonth, PageRequest request) {
|
||||
LambdaQueryWrapper<Diary> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(Diary::getUserId, userId)
|
||||
.eq(petId != null, Diary::getPetId, petId)
|
||||
.eq(diaryType != null, Diary::getDiaryType, diaryType)
|
||||
.likeRight(StringUtils.hasText(yearMonth), Diary::getHappenDate, yearMonth) // 形如 2025-06
|
||||
.orderByDesc(Diary::getHappenDate, Diary::getCreateTime);
|
||||
|
||||
IPage<Diary> page = this.page(new Page<>(request.getPageNum(), request.getPageSize()), wrapper);
|
||||
return new PageResult<>(page.getTotal(), request.getPageNum(), request.getPageSize(), page.getRecords());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package com.pawtrace.server.modules.feedback.controller;
|
||||
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import com.pawtrace.server.common.result.Result;
|
||||
import com.pawtrace.server.modules.feedback.entity.Feedback;
|
||||
import com.pawtrace.server.modules.feedback.service.FeedbackService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@Tag(name = "意见反馈")
|
||||
@RestController
|
||||
@RequestMapping("/feedback")
|
||||
public class FeedbackController {
|
||||
|
||||
private final FeedbackService feedbackService;
|
||||
|
||||
public FeedbackController(FeedbackService feedbackService) {
|
||||
this.feedbackService = feedbackService;
|
||||
}
|
||||
|
||||
@Operation(summary = "提交反馈")
|
||||
@PostMapping("/submit")
|
||||
public Result<Boolean> submit(@RequestBody Feedback fb) {
|
||||
fb.setUserId(StpUtil.getLoginIdAsLong());
|
||||
return Result.success(feedbackService.save(fb));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
package com.pawtrace.server.modules.feedback.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.pawtrace.server.entity.BaseEntity;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.io.Serial;
|
||||
|
||||
/**
|
||||
* 用户意见反馈
|
||||
*
|
||||
* @author PawTrace Team
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("pt_feedback")
|
||||
@Schema(description = "意见反馈")
|
||||
public class Feedback extends BaseEntity {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "用户ID")
|
||||
private Long userId;
|
||||
|
||||
@Schema(description = "反馈类型 1建议 2问题 3其他")
|
||||
private Integer fbType;
|
||||
|
||||
@Schema(description = "内容")
|
||||
private String content;
|
||||
|
||||
@Schema(description = "联系方式(可选)")
|
||||
private String contact;
|
||||
|
||||
@Schema(description = "图片URL(JSON数组)")
|
||||
private String images;
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.pawtrace.server.modules.feedback.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.pawtrace.server.modules.feedback.entity.Feedback;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface FeedbackMapper extends BaseMapper<Feedback> {}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package com.pawtrace.server.modules.feedback.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.pawtrace.server.modules.feedback.entity.Feedback;
|
||||
|
||||
public interface FeedbackService extends IService<Feedback> {}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package com.pawtrace.server.modules.feedback.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.pawtrace.server.modules.feedback.entity.Feedback;
|
||||
import com.pawtrace.server.modules.feedback.mapper.FeedbackMapper;
|
||||
import com.pawtrace.server.modules.feedback.service.FeedbackService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class FeedbackServiceImpl extends ServiceImpl<FeedbackMapper, Feedback> implements FeedbackService {}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
package com.pawtrace.server.modules.file.controller;
|
||||
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import com.pawtrace.server.common.constant.CommonConstants;
|
||||
import com.pawtrace.server.common.constant.ResultCode;
|
||||
import com.pawtrace.server.common.exception.BusinessException;
|
||||
import com.pawtrace.server.common.result.Result;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
/**
|
||||
* 文件上传 Controller
|
||||
* <p>
|
||||
* 当前实现为本地存储,生产环境可切换到 阿里云 OSS / 腾讯云 COS / 七牛云 / MinIO / S3。
|
||||
*
|
||||
* @author PawTrace Team
|
||||
*/
|
||||
@Slf4j
|
||||
@Tag(name = "文件上传")
|
||||
@RestController
|
||||
@RequestMapping("/file")
|
||||
public class FileController {
|
||||
|
||||
@Value("${oss.local.path:./upload/}")
|
||||
private String localPath;
|
||||
|
||||
@Value("${oss.local.domain:http://localhost:8080/api/file/}")
|
||||
private String domain;
|
||||
|
||||
@Operation(summary = "上传单个文件")
|
||||
@PostMapping("/upload")
|
||||
public Result<String> upload(@RequestParam("file") MultipartFile file) throws IOException {
|
||||
if (file.isEmpty()) {
|
||||
throw new BusinessException(ResultCode.FILE_UPLOAD_FAILED);
|
||||
}
|
||||
String original = file.getOriginalFilename();
|
||||
String ext = original != null && original.contains(".")
|
||||
? original.substring(original.lastIndexOf('.'))
|
||||
: "";
|
||||
String dateDir = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd"));
|
||||
String filename = IdUtil.simpleUUID() + ext;
|
||||
File target = new File(localPath + dateDir + "/" + filename);
|
||||
FileUtil.mkParentDirs(target);
|
||||
file.transferTo(target);
|
||||
String url = domain + dateDir + "/" + filename;
|
||||
log.info("文件上传: {} -> {}", original, url);
|
||||
return Result.success(url);
|
||||
}
|
||||
|
||||
@Operation(summary = "静态资源回显(本地存储)")
|
||||
@GetMapping("/**")
|
||||
public void staticResource() {
|
||||
// 由 WebConfig 的静态资源映射处理
|
||||
throw new BusinessException(ResultCode.NOT_FOUND);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
package com.pawtrace.server.modules.health.controller;
|
||||
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import com.pawtrace.server.common.result.Result;
|
||||
import com.pawtrace.server.modules.health.entity.Vaccine;
|
||||
import com.pawtrace.server.modules.health.entity.Deworm;
|
||||
import com.pawtrace.server.modules.health.entity.VisitRecord;
|
||||
import com.pawtrace.server.modules.health.service.HealthService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* 健康医疗本 Controller
|
||||
*
|
||||
* @author PawTrace Team
|
||||
*/
|
||||
@Tag(name = "健康医疗")
|
||||
@RestController
|
||||
@RequestMapping("/health")
|
||||
public class HealthController {
|
||||
|
||||
private final HealthService healthService;
|
||||
|
||||
public HealthController(HealthService healthService) {
|
||||
this.healthService = healthService;
|
||||
}
|
||||
|
||||
@Operation(summary = "新增/更新疫苗")
|
||||
@PostMapping("/vaccine/save")
|
||||
public Result<Boolean> saveVaccine(@RequestBody Vaccine v) {
|
||||
v.setUserId(StpUtil.getLoginIdAsLong());
|
||||
return Result.success(healthService.vaccine().saveOrUpdate(v));
|
||||
}
|
||||
|
||||
@Operation(summary = "删除疫苗")
|
||||
@DeleteMapping("/vaccine/{id}")
|
||||
public Result<Boolean> delVaccine(@PathVariable Long id) {
|
||||
return Result.success(healthService.vaccine().removeById(id));
|
||||
}
|
||||
|
||||
@Operation(summary = "新增/更新驱虫")
|
||||
@PostMapping("/deworm/save")
|
||||
public Result<Boolean> saveDeworm(@RequestBody Deworm d) {
|
||||
d.setUserId(StpUtil.getLoginIdAsLong());
|
||||
return Result.success(healthService.deworm().saveOrUpdate(d));
|
||||
}
|
||||
|
||||
@Operation(summary = "删除驱虫")
|
||||
@DeleteMapping("/deworm/{id}")
|
||||
public Result<Boolean> delDeworm(@PathVariable Long id) {
|
||||
return Result.success(healthService.deworm().removeById(id));
|
||||
}
|
||||
|
||||
@Operation(summary = "新增/更新就诊")
|
||||
@PostMapping("/visit/save")
|
||||
public Result<Boolean> saveVisit(@RequestBody VisitRecord v) {
|
||||
v.setUserId(StpUtil.getLoginIdAsLong());
|
||||
return Result.success(healthService.visit().saveOrUpdate(v));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
package com.pawtrace.server.modules.health.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.pawtrace.server.entity.BaseEntity;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.time.LocalDate;
|
||||
|
||||
/**
|
||||
* 驱虫记录
|
||||
*
|
||||
* @author PawTrace Team
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("pt_deworm")
|
||||
@Schema(description = "驱虫记录")
|
||||
public class Deworm extends BaseEntity {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "用户ID")
|
||||
private Long userId;
|
||||
|
||||
@Schema(description = "宠物ID")
|
||||
private Long petId;
|
||||
|
||||
@Schema(description = "驱虫药名称")
|
||||
private String drugName;
|
||||
|
||||
@Schema(description = "类型 1体内 2体外")
|
||||
private Integer drugType;
|
||||
|
||||
@Schema(description = "用药日期")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
|
||||
private LocalDate useDate;
|
||||
|
||||
@Schema(description = "下次用药日期")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
|
||||
private LocalDate nextDate;
|
||||
|
||||
@Schema(description = "包装照片")
|
||||
private String drugImage;
|
||||
|
||||
@Schema(description = "提醒开关")
|
||||
private Integer reminderEnabled;
|
||||
|
||||
@Schema(description = "提前提醒天数")
|
||||
private Integer remindBeforeDays;
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
package com.pawtrace.server.modules.health.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.pawtrace.server.entity.BaseEntity;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.time.LocalDate;
|
||||
|
||||
/**
|
||||
* 疫苗记录
|
||||
*
|
||||
* @author PawTrace Team
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("pt_vaccine")
|
||||
@Schema(description = "疫苗记录")
|
||||
public class Vaccine extends BaseEntity {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "用户ID")
|
||||
private Long userId;
|
||||
|
||||
@Schema(description = "宠物ID")
|
||||
private Long petId;
|
||||
|
||||
@Schema(description = "疫苗名称")
|
||||
private String vaccineName;
|
||||
|
||||
@Schema(description = "接种日期")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
|
||||
private LocalDate injectDate;
|
||||
|
||||
@Schema(description = "下次接种日期")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
|
||||
private LocalDate nextDate;
|
||||
|
||||
@Schema(description = "疫苗本照片URL")
|
||||
private String certImage;
|
||||
|
||||
@Schema(description = "提醒 0关闭 1开启")
|
||||
private Integer reminderEnabled;
|
||||
|
||||
@Schema(description = "提前提醒天数 1/3/7")
|
||||
private Integer remindBeforeDays;
|
||||
|
||||
@Schema(description = "备注")
|
||||
private String remark;
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
package com.pawtrace.server.modules.health.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.pawtrace.server.entity.BaseEntity;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
|
||||
/**
|
||||
* 就诊记录
|
||||
*
|
||||
* @author PawTrace Team
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("pt_visit_record")
|
||||
@Schema(description = "就诊记录")
|
||||
public class VisitRecord extends BaseEntity {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "用户ID")
|
||||
private Long userId;
|
||||
|
||||
@Schema(description = "宠物ID")
|
||||
private Long petId;
|
||||
|
||||
@Schema(description = "医院名称")
|
||||
private String hospital;
|
||||
|
||||
@Schema(description = "就诊原因")
|
||||
private String reason;
|
||||
|
||||
@Schema(description = "诊断结果")
|
||||
private String diagnosis;
|
||||
|
||||
@Schema(description = "费用(可选)")
|
||||
private BigDecimal cost;
|
||||
|
||||
@Schema(description = "就诊日期")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
|
||||
private LocalDate visitDate;
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.pawtrace.server.modules.health.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.pawtrace.server.modules.health.entity.Deworm;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface DewormMapper extends BaseMapper<Deworm> {}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package com.pawtrace.server.modules.health.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.pawtrace.server.modules.health.entity.Vaccine;
|
||||
import com.pawtrace.server.modules.health.entity.Deworm;
|
||||
import com.pawtrace.server.modules.health.entity.VisitRecord;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface VaccineMapper extends BaseMapper<Vaccine> {}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.pawtrace.server.modules.health.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.pawtrace.server.modules.health.entity.VisitRecord;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface VisitRecordMapper extends BaseMapper<VisitRecord> {}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package com.pawtrace.server.modules.health.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.pawtrace.server.modules.health.entity.Vaccine;
|
||||
import com.pawtrace.server.modules.health.entity.Deworm;
|
||||
import com.pawtrace.server.modules.health.entity.VisitRecord;
|
||||
|
||||
/**
|
||||
* 健康模块聚合 Service(疫苗/驱虫/就诊)
|
||||
*
|
||||
* @author PawTrace Team
|
||||
*/
|
||||
public interface HealthService {
|
||||
IService<Vaccine> vaccine();
|
||||
IService<Deworm> deworm();
|
||||
IService<VisitRecord> visit();
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
package com.pawtrace.server.modules.health.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.pawtrace.server.modules.health.entity.Vaccine;
|
||||
import com.pawtrace.server.modules.health.entity.Deworm;
|
||||
import com.pawtrace.server.modules.health.entity.VisitRecord;
|
||||
import com.pawtrace.server.modules.health.mapper.VaccineMapper;
|
||||
import com.pawtrace.server.modules.health.mapper.DewormMapper;
|
||||
import com.pawtrace.server.modules.health.mapper.VisitRecordMapper;
|
||||
import com.pawtrace.server.modules.health.service.HealthService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* 健康 Service 聚合(可由多个 Service 拆分,此处为统一入口)
|
||||
*
|
||||
* @author PawTrace Team
|
||||
*/
|
||||
@Service
|
||||
public class HealthServiceImpl implements HealthService {
|
||||
|
||||
private final VaccineServiceImpl vaccineService;
|
||||
private final DewormServiceImpl dewormService;
|
||||
private final VisitRecordServiceImpl visitService;
|
||||
|
||||
public HealthServiceImpl(VaccineServiceImpl vaccineService,
|
||||
DewormServiceImpl dewormService,
|
||||
VisitRecordServiceImpl visitService) {
|
||||
this.vaccineService = vaccineService;
|
||||
this.dewormService = dewormService;
|
||||
this.visitService = visitService;
|
||||
}
|
||||
|
||||
@Override public IService<Vaccine> vaccine() { return vaccineService; }
|
||||
@Override public IService<Deworm> deworm() { return dewormService; }
|
||||
@Override public IService<VisitRecord> visit() { return visitService; }
|
||||
|
||||
@Service
|
||||
public static class VaccineServiceImpl extends ServiceImpl<VaccineMapper, Vaccine> {}
|
||||
@Service
|
||||
public static class DewormServiceImpl extends ServiceImpl<DewormMapper, Deworm> {}
|
||||
@Service
|
||||
public static class VisitRecordServiceImpl extends ServiceImpl<VisitRecordMapper, VisitRecord> {}
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
package com.pawtrace.server.modules.inventory.controller;
|
||||
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import com.pawtrace.server.common.result.Result;
|
||||
import com.pawtrace.server.modules.inventory.entity.Inventory;
|
||||
import com.pawtrace.server.modules.inventory.entity.InventoryLog;
|
||||
import com.pawtrace.server.modules.inventory.service.InventoryService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Tag(name = "物资管家")
|
||||
@RestController
|
||||
@RequestMapping("/inventory")
|
||||
public class InventoryController {
|
||||
|
||||
private final InventoryService inventoryService;
|
||||
|
||||
public InventoryController(InventoryService inventoryService) {
|
||||
this.inventoryService = inventoryService;
|
||||
}
|
||||
|
||||
@Operation(summary = "我的物资列表")
|
||||
@GetMapping("/list")
|
||||
public Result<List<Inventory>> list() {
|
||||
Long uid = StpUtil.getLoginIdAsLong();
|
||||
return Result.success(inventoryService.lambdaQuery().eq(Inventory::getUserId, uid).list());
|
||||
}
|
||||
|
||||
@Operation(summary = "新增/更新物资")
|
||||
@PostMapping("/save")
|
||||
public Result<Boolean> save(@RequestBody Inventory inv) {
|
||||
inv.setUserId(StpUtil.getLoginIdAsLong());
|
||||
return Result.success(inventoryService.saveOrUpdate(inv));
|
||||
}
|
||||
|
||||
@Operation(summary = "低库存预警列表")
|
||||
@GetMapping("/low-stock")
|
||||
public Result<List<Inventory>> lowStock() {
|
||||
return Result.success(inventoryService.listLowStock(StpUtil.getLoginIdAsLong()));
|
||||
}
|
||||
|
||||
@Operation(summary = "记录一次消耗")
|
||||
@PostMapping("/consume")
|
||||
public Result<Boolean> consume(@RequestBody InventoryLog log) {
|
||||
log.setUserId(StpUtil.getLoginIdAsLong());
|
||||
return Result.success(inventoryService.consume(log));
|
||||
}
|
||||
|
||||
@Operation(summary = "删除物资")
|
||||
@DeleteMapping("/{id}")
|
||||
public Result<Boolean> delete(@PathVariable Long id) {
|
||||
return Result.success(inventoryService.removeById(id));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
package com.pawtrace.server.modules.inventory.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.pawtrace.server.entity.BaseEntity;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 物资库存
|
||||
*
|
||||
* @author PawTrace Team
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("pt_inventory")
|
||||
@Schema(description = "物资库存")
|
||||
public class Inventory extends BaseEntity {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "用户ID")
|
||||
private Long userId;
|
||||
|
||||
@Schema(description = "分类 1粮 2罐头 3零食 4猫砂 5药品 6其他")
|
||||
private Integer category;
|
||||
|
||||
@Schema(description = "名称")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "当前库存")
|
||||
private BigDecimal stock;
|
||||
|
||||
@Schema(description = "单位 g/ml/袋/盒")
|
||||
private String unit;
|
||||
|
||||
@Schema(description = "低库存阈值")
|
||||
private BigDecimal lowStockThreshold;
|
||||
|
||||
@Schema(description = "封面图")
|
||||
private String coverImage;
|
||||
|
||||
@Schema(description = "备注")
|
||||
private String remark;
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
package com.pawtrace.server.modules.inventory.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.pawtrace.server.entity.BaseEntity;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 物资消耗记录
|
||||
*
|
||||
* @author PawTrace Team
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("pt_inventory_log")
|
||||
@Schema(description = "物资消耗记录")
|
||||
public class InventoryLog extends BaseEntity {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "物资ID")
|
||||
private Long inventoryId;
|
||||
|
||||
@Schema(description = "用户ID")
|
||||
private Long userId;
|
||||
|
||||
@Schema(description = "消耗数量(正数)")
|
||||
private BigDecimal quantity;
|
||||
|
||||
@Schema(description = "备注 如:吃了半袋")
|
||||
private String remark;
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.pawtrace.server.modules.inventory.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.pawtrace.server.modules.inventory.entity.InventoryLog;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface InventoryLogMapper extends BaseMapper<InventoryLog> {}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package com.pawtrace.server.modules.inventory.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.pawtrace.server.modules.inventory.entity.Inventory;
|
||||
import com.pawtrace.server.modules.inventory.entity.InventoryLog;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface InventoryMapper extends BaseMapper<Inventory> {}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package com.pawtrace.server.modules.inventory.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.pawtrace.server.modules.inventory.entity.Inventory;
|
||||
import com.pawtrace.server.modules.inventory.entity.InventoryLog;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface InventoryService extends IService<Inventory> {
|
||||
|
||||
/** 当前用户的低库存物品 */
|
||||
List<Inventory> listLowStock(Long userId);
|
||||
|
||||
/** 记录一次消耗,自动扣减库存 */
|
||||
boolean consume(InventoryLog log);
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
package com.pawtrace.server.modules.inventory.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.pawtrace.server.common.exception.BusinessException;
|
||||
import com.pawtrace.server.common.constant.ResultCode;
|
||||
import com.pawtrace.server.modules.inventory.entity.Inventory;
|
||||
import com.pawtrace.server.modules.inventory.entity.InventoryLog;
|
||||
import com.pawtrace.server.modules.inventory.mapper.InventoryLogMapper;
|
||||
import com.pawtrace.server.modules.inventory.mapper.InventoryMapper;
|
||||
import com.pawtrace.server.modules.inventory.service.InventoryService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class InventoryServiceImpl extends ServiceImpl<InventoryMapper, Inventory> implements InventoryService {
|
||||
|
||||
private final InventoryLogMapper logMapper;
|
||||
|
||||
public InventoryServiceImpl(InventoryLogMapper logMapper) {
|
||||
this.logMapper = logMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Inventory> listLowStock(Long userId) {
|
||||
return this.list(new LambdaQueryWrapper<Inventory>()
|
||||
.eq(Inventory::getUserId, userId)
|
||||
.and(w -> w.apply("stock <= low_stock_threshold")));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean consume(InventoryLog log) {
|
||||
Inventory item = this.getById(log.getInventoryId());
|
||||
if (item == null) {
|
||||
throw new BusinessException(ResultCode.INVENTORY_NOT_FOUND);
|
||||
}
|
||||
BigDecimal remain = item.getStock().subtract(log.getQuantity());
|
||||
item.setStock(remain);
|
||||
this.updateById(item);
|
||||
return logMapper.insert(log) > 0;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
package com.pawtrace.server.modules.lifecard.controller;
|
||||
|
||||
import com.pawtrace.server.common.result.Result;
|
||||
import com.pawtrace.server.modules.lifecard.entity.LifeCard;
|
||||
import com.pawtrace.server.modules.lifecard.service.LifeCardService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 宠物生活信息卡 Controller
|
||||
*
|
||||
* @author PawTrace Team
|
||||
*/
|
||||
@Tag(name = "宠物生活信息卡")
|
||||
@RestController
|
||||
@RequestMapping("/life-card")
|
||||
public class LifeCardController {
|
||||
|
||||
private final LifeCardService lifeCardService;
|
||||
|
||||
public LifeCardController(LifeCardService lifeCardService) {
|
||||
this.lifeCardService = lifeCardService;
|
||||
}
|
||||
|
||||
@Operation(summary = "按分类查询")
|
||||
@GetMapping("/category/{category}")
|
||||
public Result<List<LifeCard>> listByCategory(@PathVariable Integer category) {
|
||||
return Result.success(lifeCardService.listByCategory(category));
|
||||
}
|
||||
|
||||
@Operation(summary = "详情")
|
||||
@GetMapping("/{id}")
|
||||
public Result<LifeCard> detail(@PathVariable Long id) {
|
||||
return Result.success(lifeCardService.getById(id));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
package com.pawtrace.server.modules.lifecard.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.pawtrace.server.entity.BaseEntity;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 宠物生活信息卡(商家/地点)
|
||||
*
|
||||
* @author PawTrace Team
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("pt_life_card")
|
||||
@Schema(description = "生活信息卡")
|
||||
public class LifeCard extends BaseEntity {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "分类 1医院 2餐厅 3公园 4宠物店 5美容")
|
||||
private Integer category;
|
||||
|
||||
@Schema(description = "名称")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "封面图")
|
||||
private String cover;
|
||||
|
||||
@Schema(description = "地址")
|
||||
private String address;
|
||||
|
||||
@Schema(description = "联系电话")
|
||||
private String phone;
|
||||
|
||||
@Schema(description = "简介")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "经度(腾讯地图GCJ02)")
|
||||
private BigDecimal longitude;
|
||||
|
||||
@Schema(description = "纬度(腾讯地图GCJ02)")
|
||||
private BigDecimal latitude;
|
||||
|
||||
@Schema(description = "排序")
|
||||
private Integer sortOrder;
|
||||
|
||||
@Schema(description = "状态 0下架 1上架")
|
||||
private Integer status;
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.pawtrace.server.modules.lifecard.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.pawtrace.server.modules.lifecard.entity.LifeCard;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface LifeCardMapper extends BaseMapper<LifeCard> {}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.pawtrace.server.modules.lifecard.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.pawtrace.server.modules.lifecard.entity.LifeCard;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface LifeCardService extends IService<LifeCard> {
|
||||
|
||||
/** 按分类查询上架的生活信息卡 */
|
||||
List<LifeCard> listByCategory(Integer category);
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
package com.pawtrace.server.modules.lifecard.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.pawtrace.server.common.constant.CommonConstants;
|
||||
import com.pawtrace.server.modules.lifecard.entity.LifeCard;
|
||||
import com.pawtrace.server.modules.lifecard.mapper.LifeCardMapper;
|
||||
import com.pawtrace.server.modules.lifecard.service.LifeCardService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class LifeCardServiceImpl extends ServiceImpl<LifeCardMapper, LifeCard> implements LifeCardService {
|
||||
|
||||
@Override
|
||||
public List<LifeCard> listByCategory(Integer category) {
|
||||
if (category == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return this.list(new LambdaQueryWrapper<LifeCard>()
|
||||
.eq(LifeCard::getCategory, category)
|
||||
.eq(LifeCard::getStatus, CommonConstants.STATUS_ENABLED)
|
||||
.orderByAsc(LifeCard::getSortOrder));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
package com.pawtrace.server.modules.pet.controller;
|
||||
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import com.pawtrace.server.common.result.Result;
|
||||
import com.pawtrace.server.modules.pet.entity.Pet;
|
||||
import com.pawtrace.server.modules.pet.service.PetService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 宠物档案 Controller
|
||||
*
|
||||
* @author PawTrace Team
|
||||
*/
|
||||
@Tag(name = "宠物档案")
|
||||
@RestController
|
||||
@RequestMapping("/pet")
|
||||
public class PetController {
|
||||
|
||||
private final PetService petService;
|
||||
|
||||
public PetController(PetService petService) {
|
||||
this.petService = petService;
|
||||
}
|
||||
|
||||
@Operation(summary = "我的宠物列表")
|
||||
@GetMapping("/list")
|
||||
public Result<List<Pet>> list() {
|
||||
return Result.success(petService.listMyPets(StpUtil.getLoginIdAsLong()));
|
||||
}
|
||||
|
||||
@Operation(summary = "新增/更新宠物")
|
||||
@PostMapping("/save")
|
||||
public Result<Boolean> save(@RequestBody Pet pet) {
|
||||
pet.setUserId(StpUtil.getLoginIdAsLong());
|
||||
return Result.success(petService.saveOrUpdate(pet));
|
||||
}
|
||||
|
||||
@Operation(summary = "删除宠物")
|
||||
@DeleteMapping("/{id}")
|
||||
public Result<Boolean> delete(@PathVariable Long id) {
|
||||
return Result.success(petService.removeById(id));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
package com.pawtrace.server.modules.pet.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.pawtrace.server.entity.BaseEntity;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
|
||||
/**
|
||||
* 宠物档案
|
||||
*
|
||||
* @author PawTrace Team
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("pt_pet")
|
||||
@Schema(description = "宠物档案")
|
||||
public class Pet extends BaseEntity {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "所属用户ID")
|
||||
private Long userId;
|
||||
|
||||
@Schema(description = "昵称")
|
||||
private String nickname;
|
||||
|
||||
@Schema(description = "品种")
|
||||
private String breed;
|
||||
|
||||
@Schema(description = "种类 1狗 2猫 3其他")
|
||||
private Integer species;
|
||||
|
||||
@Schema(description = "性别 0未知 1公 2母")
|
||||
private Integer gender;
|
||||
|
||||
@Schema(description = "生日")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
|
||||
private LocalDate birthday;
|
||||
|
||||
@Schema(description = "当前体重(kg)")
|
||||
private BigDecimal weight;
|
||||
|
||||
@Schema(description = "头像URL")
|
||||
private String avatar;
|
||||
|
||||
@Schema(description = "性格标签(逗号分隔)")
|
||||
private String personalityTags;
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
package com.pawtrace.server.modules.pet.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.pawtrace.server.entity.BaseEntity;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
|
||||
/**
|
||||
* 体重历史记录
|
||||
*
|
||||
* @author PawTrace Team
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("pt_pet_weight")
|
||||
@Schema(description = "体重记录")
|
||||
public class PetWeight extends BaseEntity {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "宠物ID")
|
||||
private Long petId;
|
||||
|
||||
@Schema(description = "用户ID")
|
||||
private Long userId;
|
||||
|
||||
@Schema(description = "体重(kg)")
|
||||
private BigDecimal weight;
|
||||
|
||||
@Schema(description = "记录日期")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
|
||||
private LocalDate recordDate;
|
||||
|
||||
@Schema(description = "备注")
|
||||
private String remark;
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package com.pawtrace.server.modules.pet.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.pawtrace.server.modules.pet.entity.Pet;
|
||||
import com.pawtrace.server.modules.pet.entity.PetWeight;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 宠物 Mapper
|
||||
*
|
||||
* @author PawTrace Team
|
||||
*/
|
||||
@Mapper
|
||||
public interface PetMapper extends BaseMapper<Pet> {
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package com.pawtrace.server.modules.pet.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.pawtrace.server.modules.pet.entity.PetWeight;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 体重记录 Mapper
|
||||
*
|
||||
* @author PawTrace Team
|
||||
*/
|
||||
@Mapper
|
||||
public interface PetWeightMapper extends BaseMapper<PetWeight> {
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
package com.pawtrace.server.modules.pet.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.pawtrace.server.common.page.PageResult;
|
||||
import com.pawtrace.server.modules.pet.entity.Pet;
|
||||
import com.pawtrace.server.modules.pet.entity.PetWeight;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 宠物 Service
|
||||
*
|
||||
* @author PawTrace Team
|
||||
*/
|
||||
public interface PetService extends IService<Pet> {
|
||||
|
||||
/** 当前用户的宠物列表 */
|
||||
List<Pet> listMyPets(Long userId);
|
||||
|
||||
/** 体重历史分页 */
|
||||
PageResult<PetWeight> pageWeight(Long userId, Long petId, Long pageNum, Long pageSize);
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.pawtrace.server.modules.pet.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.pawtrace.server.modules.pet.entity.PetWeight;
|
||||
|
||||
/**
|
||||
* 体重 Service
|
||||
*
|
||||
* @author PawTrace Team
|
||||
*/
|
||||
public interface PetWeightService extends IService<PetWeight> {
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
package com.pawtrace.server.modules.pet.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.pawtrace.server.common.page.PageResult;
|
||||
import com.pawtrace.server.modules.pet.entity.Pet;
|
||||
import com.pawtrace.server.modules.pet.entity.PetWeight;
|
||||
import com.pawtrace.server.modules.pet.mapper.PetMapper;
|
||||
import com.pawtrace.server.modules.pet.mapper.PetWeightMapper;
|
||||
import com.pawtrace.server.modules.pet.service.PetService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 宠物 Service 实现
|
||||
*
|
||||
* @author PawTrace Team
|
||||
*/
|
||||
@Service
|
||||
public class PetServiceImpl extends ServiceImpl<PetMapper, Pet> implements PetService {
|
||||
|
||||
private final PetWeightMapper petWeightMapper;
|
||||
|
||||
public PetServiceImpl(PetWeightMapper petWeightMapper) {
|
||||
this.petWeightMapper = petWeightMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Pet> listMyPets(Long userId) {
|
||||
return this.list(new LambdaQueryWrapper<Pet>().eq(Pet::getUserId, userId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<PetWeight> pageWeight(Long userId, Long petId, Long pageNum, Long pageSize) {
|
||||
IPage<PetWeight> page = petWeightMapper.selectPage(new Page<>(pageNum, pageSize),
|
||||
new LambdaQueryWrapper<PetWeight>()
|
||||
.eq(PetWeight::getUserId, userId)
|
||||
.eq(petId != null, PetWeight::getPetId, petId)
|
||||
.orderByDesc(PetWeight::getRecordDate));
|
||||
return new PageResult<>(page.getTotal(), pageNum, pageSize, page.getRecords());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package com.pawtrace.server.modules.pet.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.pawtrace.server.modules.pet.entity.PetWeight;
|
||||
import com.pawtrace.server.modules.pet.mapper.PetWeightMapper;
|
||||
import com.pawtrace.server.modules.pet.service.PetWeightService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* 体重 Service 实现
|
||||
*
|
||||
* @author PawTrace Team
|
||||
*/
|
||||
@Service
|
||||
public class PetWeightServiceImpl extends ServiceImpl<PetWeightMapper, PetWeight> implements PetWeightService {
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
package com.pawtrace.server.modules.reminder.scheduled;
|
||||
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.pawtrace.server.modules.health.entity.Vaccine;
|
||||
import com.pawtrace.server.modules.health.entity.Deworm;
|
||||
import com.pawtrace.server.modules.health.mapper.VaccineMapper;
|
||||
import com.pawtrace.server.modules.health.mapper.DewormMapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 提醒扫描任务
|
||||
* <p>
|
||||
* 每天 08:00 扫描即将到期的疫苗 / 驱虫,通过微信订阅消息推送给用户。
|
||||
* 实际推送实现依赖 wx-java miniapp,此处仅负责检索待推送列表。
|
||||
*
|
||||
* @author PawTrace Team
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class ReminderScheduledTask {
|
||||
|
||||
private final VaccineMapper vaccineMapper;
|
||||
private final DewormMapper dewormMapper;
|
||||
|
||||
@Scheduled(cron = "0 0 8 * * ?")
|
||||
public void scanUpcomingReminders() {
|
||||
LocalDate today = LocalDate.now();
|
||||
// 1) 即将到期(提前1天)
|
||||
scanVaccine(today, 1);
|
||||
scanVaccine(today, 3);
|
||||
scanVaccine(today, 7);
|
||||
scanDeworm(today, 1);
|
||||
scanDeworm(today, 3);
|
||||
scanDeworm(today, 7);
|
||||
}
|
||||
|
||||
private void scanVaccine(LocalDate today, int beforeDays) {
|
||||
LocalDate target = today.plusDays(beforeDays);
|
||||
List<Vaccine> list = vaccineMapper.selectList(Wrappers.<Vaccine>lambdaQuery()
|
||||
.eq(Vaccine::getReminderEnabled, 1)
|
||||
.eq(Vaccine::getRemindBeforeDays, beforeDays)
|
||||
.eq(Vaccine::getNextDate, target));
|
||||
if (!list.isEmpty()) {
|
||||
log.info("[提醒] 疫苗 {} 天后到期, 命中 {} 条", beforeDays, list.size());
|
||||
// TODO: 调用 wx-java 推送订阅消息
|
||||
}
|
||||
}
|
||||
|
||||
private void scanDeworm(LocalDate today, int beforeDays) {
|
||||
LocalDate target = today.plusDays(beforeDays);
|
||||
List<Deworm> list = dewormMapper.selectList(Wrappers.<Deworm>lambdaQuery()
|
||||
.eq(Deworm::getReminderEnabled, 1)
|
||||
.eq(Deworm::getRemindBeforeDays, beforeDays)
|
||||
.eq(Deworm::getNextDate, target));
|
||||
if (!list.isEmpty()) {
|
||||
log.info("[提醒] 驱虫 {} 天后到期, 命中 {} 条", beforeDays, list.size());
|
||||
// TODO: 调用 wx-java 推送订阅消息
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
package com.pawtrace.server.modules.timemachine.controller;
|
||||
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import com.pawtrace.server.common.result.Result;
|
||||
import com.pawtrace.server.modules.timemachine.entity.TimeMachineWork;
|
||||
import com.pawtrace.server.modules.timemachine.service.TimeMachineWorkService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 时光机工坊 Controller
|
||||
* 注:核心处理(对比/滤镜/视频合成)在客户端完成,服务端仅保存作品元数据
|
||||
*
|
||||
* @author PawTrace Team
|
||||
*/
|
||||
@Tag(name = "时光机工坊")
|
||||
@RestController
|
||||
@RequestMapping("/timemachine")
|
||||
public class TimeMachineController {
|
||||
|
||||
private final TimeMachineWorkService workService;
|
||||
|
||||
public TimeMachineController(TimeMachineWorkService workService) {
|
||||
this.workService = workService;
|
||||
}
|
||||
|
||||
@Operation(summary = "我的作品列表")
|
||||
@GetMapping("/list")
|
||||
public Result<List<TimeMachineWork>> list(@RequestParam(required = false) Integer workType,
|
||||
@RequestParam(required = false) Long petId) {
|
||||
return Result.success(workService.lambdaQuery()
|
||||
.eq(TimeMachineWork::getUserId, StpUtil.getLoginIdAsLong())
|
||||
.eq(workType != null, TimeMachineWork::getWorkType, workType)
|
||||
.eq(petId != null, TimeMachineWork::getPetId, petId)
|
||||
.orderByDesc(TimeMachineWork::getCreateTime)
|
||||
.list());
|
||||
}
|
||||
|
||||
@Operation(summary = "保存作品")
|
||||
@PostMapping("/save")
|
||||
public Result<Boolean> save(@RequestBody TimeMachineWork work) {
|
||||
work.setUserId(StpUtil.getLoginIdAsLong());
|
||||
return Result.success(workService.saveOrUpdate(work));
|
||||
}
|
||||
|
||||
@Operation(summary = "删除作品")
|
||||
@DeleteMapping("/{id}")
|
||||
public Result<Boolean> delete(@PathVariable Long id) {
|
||||
return Result.success(workService.removeById(id));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
package com.pawtrace.server.modules.timemachine.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.pawtrace.server.entity.BaseEntity;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.io.Serial;
|
||||
|
||||
/**
|
||||
* 时光机工坊作品
|
||||
*
|
||||
* @author PawTrace Team
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("pt_timemachine_work")
|
||||
@Schema(description = "时光机作品")
|
||||
public class TimeMachineWork extends BaseEntity {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "用户ID")
|
||||
private Long userId;
|
||||
|
||||
@Schema(description = "作品类型 1同角度对比 2AI滤镜 3时光机视频")
|
||||
private Integer workType;
|
||||
|
||||
@Schema(description = "关联宠物ID")
|
||||
private Long petId;
|
||||
|
||||
@Schema(description = "标题")
|
||||
private String title;
|
||||
|
||||
@Schema(description = "使用的素材URL列表(JSON数组)")
|
||||
private String sourceUrls;
|
||||
|
||||
@Schema(description = "结果URL(图片/视频)")
|
||||
private String resultUrl;
|
||||
|
||||
@Schema(description = "模板标识")
|
||||
private String templateKey;
|
||||
|
||||
@Schema(description = "备注")
|
||||
private String remark;
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.pawtrace.server.modules.timemachine.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.pawtrace.server.modules.timemachine.entity.TimeMachineWork;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface TimeMachineWorkMapper extends BaseMapper<TimeMachineWork> {}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package com.pawtrace.server.modules.timemachine.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.pawtrace.server.modules.timemachine.entity.TimeMachineWork;
|
||||
|
||||
public interface TimeMachineWorkService extends IService<TimeMachineWork> {}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package com.pawtrace.server.modules.timemachine.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.pawtrace.server.modules.timemachine.entity.TimeMachineWork;
|
||||
import com.pawtrace.server.modules.timemachine.mapper.TimeMachineWorkMapper;
|
||||
import com.pawtrace.server.modules.timemachine.service.TimeMachineWorkService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class TimeMachineWorkServiceImpl extends ServiceImpl<TimeMachineWorkMapper, TimeMachineWork> implements TimeMachineWorkService {}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
package com.pawtrace.server.modules.user.controller;
|
||||
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import com.pawtrace.server.common.result.Result;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* 用户 / 鉴权 Controller
|
||||
*
|
||||
* @author PawTrace Team
|
||||
*/
|
||||
@Tag(name = "用户认证")
|
||||
@RestController
|
||||
@RequestMapping("/auth")
|
||||
public class AuthController {
|
||||
|
||||
@Operation(summary = "微信小程序登录(占位)")
|
||||
@GetMapping("/login")
|
||||
public Result<String> login() {
|
||||
// TODO: 接入 wx-java miniapp, 校验 code -> openid -> 自动注册
|
||||
StpUtil.login(10001L);
|
||||
return Result.success(StpUtil.getTokenValue());
|
||||
}
|
||||
|
||||
@Operation(summary = "退出登录")
|
||||
@GetMapping("/logout")
|
||||
public Result<Void> logout() {
|
||||
StpUtil.logout();
|
||||
return Result.success();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
package com.pawtrace.server.modules.user.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.pawtrace.server.entity.BaseEntity;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.io.Serial;
|
||||
|
||||
/**
|
||||
* 用户
|
||||
*
|
||||
* @author PawTrace Team
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("pt_user")
|
||||
@Schema(description = "用户")
|
||||
public class User extends BaseEntity {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "微信openid")
|
||||
private String openid;
|
||||
|
||||
@Schema(description = "微信unionid")
|
||||
private String unionid;
|
||||
|
||||
@Schema(description = "昵称")
|
||||
private String nickname;
|
||||
|
||||
@Schema(description = "头像URL")
|
||||
private String avatar;
|
||||
|
||||
@Schema(description = "性别 0未知 1男 2女")
|
||||
private Integer gender;
|
||||
|
||||
@Schema(description = "手机号")
|
||||
private String phone;
|
||||
|
||||
@Schema(description = "状态 0禁用 1启用")
|
||||
private Integer status;
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package com.pawtrace.server.modules.user.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.pawtrace.server.modules.user.entity.User;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 用户 Mapper
|
||||
*
|
||||
* @author PawTrace Team
|
||||
*/
|
||||
@Mapper
|
||||
public interface UserMapper extends BaseMapper<User> {
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package com.pawtrace.server.modules.user.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.pawtrace.server.modules.user.entity.User;
|
||||
|
||||
/**
|
||||
* 用户 Service
|
||||
*
|
||||
* @author PawTrace Team
|
||||
*/
|
||||
public interface UserService extends IService<User> {
|
||||
|
||||
/**
|
||||
* 根据 openid 查询用户
|
||||
*/
|
||||
User getByOpenid(String openid);
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
package com.pawtrace.server.modules.user.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.pawtrace.server.modules.user.entity.User;
|
||||
import com.pawtrace.server.modules.user.mapper.UserMapper;
|
||||
import com.pawtrace.server.modules.user.service.UserService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* 用户 Service 实现
|
||||
*
|
||||
* @author PawTrace Team
|
||||
*/
|
||||
@Service
|
||||
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
|
||||
|
||||
@Override
|
||||
public User getByOpenid(String openid) {
|
||||
return this.getOne(Wrappers.<User>lambdaQuery().eq(User::getOpenid, openid), false);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
spring:
|
||||
datasource:
|
||||
url: jdbc:mysql://localhost:3306/pawtrace_dev?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
|
||||
username: root
|
||||
password: root
|
||||
|
||||
logging:
|
||||
level:
|
||||
com.pawtrace: DEBUG
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
spring:
|
||||
datasource:
|
||||
url: jdbc:mysql://localhost:3306/pawtrace_prod?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=Asia/Shanghai
|
||||
username: pawtrace
|
||||
password: ${DB_PASSWORD}
|
||||
|
||||
logging:
|
||||
level:
|
||||
root: WARN
|
||||
com.pawtrace: INFO
|
||||
|
|
@ -0,0 +1,125 @@
|
|||
server:
|
||||
port: 8080
|
||||
servlet:
|
||||
context-path: /api
|
||||
|
||||
spring:
|
||||
application:
|
||||
name: server-pawtrace
|
||||
profiles:
|
||||
active: dev
|
||||
jackson:
|
||||
date-format: yyyy-MM-dd HH:mm:ss
|
||||
time-zone: GMT+8
|
||||
default-property-inclusion: non_null
|
||||
servlet:
|
||||
multipart:
|
||||
max-file-size: 50MB
|
||||
max-request-size: 100MB
|
||||
datasource:
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
url: jdbc:mysql://localhost:3306/pawtrace?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
|
||||
username: root
|
||||
password: root
|
||||
hikari:
|
||||
minimum-idle: 5
|
||||
maximum-pool-size: 20
|
||||
idle-timeout: 30000
|
||||
connection-timeout: 30000
|
||||
max-lifetime: 1800000
|
||||
data:
|
||||
redis:
|
||||
host: localhost
|
||||
port: 6379
|
||||
password:
|
||||
database: 0
|
||||
timeout: 10s
|
||||
lettuce:
|
||||
pool:
|
||||
max-active: 8
|
||||
max-wait: -1ms
|
||||
max-idle: 8
|
||||
min-idle: 0
|
||||
|
||||
mybatis-plus:
|
||||
mapper-locations: classpath*:mapper/**/*.xml
|
||||
type-aliases-package: com.pawtrace.server.entity
|
||||
configuration:
|
||||
map-underscore-to-camel-case: true
|
||||
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
|
||||
cache-enabled: false
|
||||
global-config:
|
||||
banner: false
|
||||
db-config:
|
||||
id-type: ASSIGN_ID
|
||||
logic-delete-field: deleted
|
||||
logic-delete-value: 1
|
||||
logic-not-delete-value: 0
|
||||
insert-strategy: not_null
|
||||
update-strategy: not_null
|
||||
|
||||
sa-token:
|
||||
token-name: Authorization
|
||||
timeout: 2592000
|
||||
active-timeout: -1
|
||||
is-concurrent: true
|
||||
is-share: true
|
||||
token-style: uuid
|
||||
is-log: false
|
||||
is-read-cookie: false
|
||||
is-read-header: true
|
||||
|
||||
wx:
|
||||
miniapp:
|
||||
appid: your-appid-here
|
||||
secret: your-secret-here
|
||||
msg-data-format: JSON
|
||||
|
||||
oss:
|
||||
type: local
|
||||
local:
|
||||
path: ./upload/
|
||||
domain: http://localhost:8080/api/file/
|
||||
qiniu:
|
||||
access-key: your-access-key
|
||||
secret-key: your-secret-key
|
||||
bucket: your-bucket
|
||||
domain: http://your-domain
|
||||
aliyun:
|
||||
endpoint: oss-cn-hangzhou.aliyuncs.com
|
||||
access-key-id: your-access-key-id
|
||||
access-key-secret: your-access-key-secret
|
||||
bucket-name: your-bucket-name
|
||||
tencent:
|
||||
secret-id: your-secret-id
|
||||
secret-key: your-secret-key
|
||||
region: ap-guangzhou
|
||||
bucket-name: your-bucket-name
|
||||
minio:
|
||||
endpoint: http://localhost:9000
|
||||
access-key: minioadmin
|
||||
secret-key: minioadmin
|
||||
bucket-name: pawtrace
|
||||
|
||||
springdoc:
|
||||
api-docs:
|
||||
enabled: true
|
||||
path: /v3/api-docs
|
||||
swagger-ui:
|
||||
enabled: true
|
||||
path: /swagger-ui.html
|
||||
|
||||
knife4j:
|
||||
enable: true
|
||||
setting:
|
||||
language: zh_cn
|
||||
|
||||
logging:
|
||||
level:
|
||||
root: INFO
|
||||
com.pawtrace: DEBUG
|
||||
com.baomidou.mybatisplus: INFO
|
||||
file:
|
||||
name: ./logs/server-pawtrace.log
|
||||
pattern:
|
||||
console: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"
|
||||
|
|
@ -0,0 +1,251 @@
|
|||
-- ============================================================
|
||||
-- 宠迹 PawTrace 数据库初始化脚本
|
||||
-- 数据库: MySQL 8.x
|
||||
-- 字符集: utf8mb4 / utf8mb4_unicode_ci
|
||||
-- ============================================================
|
||||
|
||||
CREATE DATABASE IF NOT EXISTS `pawtrace` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
USE `pawtrace`;
|
||||
|
||||
-- ------------------------------------------------------------
|
||||
-- 1. 用户表
|
||||
-- ------------------------------------------------------------
|
||||
DROP TABLE IF EXISTS `pt_user`;
|
||||
CREATE TABLE `pt_user` (
|
||||
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||
`openid` VARCHAR(64) DEFAULT NULL COMMENT '微信openid',
|
||||
`unionid` VARCHAR(64) DEFAULT NULL COMMENT '微信unionid',
|
||||
`nickname` VARCHAR(64) DEFAULT NULL COMMENT '昵称',
|
||||
`avatar` VARCHAR(255) DEFAULT NULL COMMENT '头像URL',
|
||||
`gender` TINYINT DEFAULT 0 COMMENT '0未知 1男 2女',
|
||||
`phone` VARCHAR(20) DEFAULT NULL COMMENT '手机号',
|
||||
`status` TINYINT DEFAULT 1 COMMENT '0禁用 1启用',
|
||||
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
`update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
`deleted` TINYINT DEFAULT 0,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_openid` (`openid`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户';
|
||||
|
||||
-- ------------------------------------------------------------
|
||||
-- 2. 宠物档案
|
||||
-- ------------------------------------------------------------
|
||||
DROP TABLE IF EXISTS `pt_pet`;
|
||||
CREATE TABLE `pt_pet` (
|
||||
`id` BIGINT NOT NULL AUTO_INCREMENT,
|
||||
`user_id` BIGINT NOT NULL,
|
||||
`nickname` VARCHAR(64) NOT NULL COMMENT '昵称',
|
||||
`breed` VARCHAR(64) DEFAULT NULL COMMENT '品种',
|
||||
`species` TINYINT DEFAULT 1 COMMENT '1狗 2猫 3其他',
|
||||
`gender` TINYINT DEFAULT 0 COMMENT '0未知 1公 2母',
|
||||
`birthday` DATE DEFAULT NULL,
|
||||
`weight` DECIMAL(8,2) DEFAULT NULL COMMENT '当前体重kg',
|
||||
`avatar` VARCHAR(255) DEFAULT NULL,
|
||||
`personality_tags` VARCHAR(255) DEFAULT NULL COMMENT '逗号分隔',
|
||||
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
`update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
`deleted` TINYINT DEFAULT 0,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_user_id` (`user_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='宠物档案';
|
||||
|
||||
-- ------------------------------------------------------------
|
||||
-- 3. 体重历史
|
||||
-- ------------------------------------------------------------
|
||||
DROP TABLE IF EXISTS `pt_pet_weight`;
|
||||
CREATE TABLE `pt_pet_weight` (
|
||||
`id` BIGINT NOT NULL AUTO_INCREMENT,
|
||||
`user_id` BIGINT NOT NULL,
|
||||
`pet_id` BIGINT NOT NULL,
|
||||
`weight` DECIMAL(8,2) NOT NULL,
|
||||
`record_date` DATE NOT NULL,
|
||||
`remark` VARCHAR(255) DEFAULT NULL,
|
||||
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
`update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
`deleted` TINYINT DEFAULT 0,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_pet_id` (`pet_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='体重记录';
|
||||
|
||||
-- ------------------------------------------------------------
|
||||
-- 4. 成长日记
|
||||
-- ------------------------------------------------------------
|
||||
DROP TABLE IF EXISTS `pt_diary`;
|
||||
CREATE TABLE `pt_diary` (
|
||||
`id` BIGINT NOT NULL AUTO_INCREMENT,
|
||||
`user_id` BIGINT NOT NULL,
|
||||
`pet_id` BIGINT DEFAULT NULL,
|
||||
`title` VARCHAR(128) DEFAULT NULL,
|
||||
`content` TEXT,
|
||||
`media_urls` JSON DEFAULT NULL COMMENT '图片/视频URL数组',
|
||||
`mood_tags` VARCHAR(255) DEFAULT NULL,
|
||||
`location` VARCHAR(255) DEFAULT NULL,
|
||||
`diary_type` TINYINT DEFAULT 1 COMMENT '1日常 2成长 3医疗 4饮食 5趣事',
|
||||
`happen_date` DATE DEFAULT NULL,
|
||||
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
`update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
`deleted` TINYINT DEFAULT 0,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_user_date` (`user_id`, `happen_date`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='成长日记';
|
||||
|
||||
-- ------------------------------------------------------------
|
||||
-- 5. 疫苗
|
||||
-- ------------------------------------------------------------
|
||||
DROP TABLE IF EXISTS `pt_vaccine`;
|
||||
CREATE TABLE `pt_vaccine` (
|
||||
`id` BIGINT NOT NULL AUTO_INCREMENT,
|
||||
`user_id` BIGINT NOT NULL,
|
||||
`pet_id` BIGINT NOT NULL,
|
||||
`vaccine_name` VARCHAR(128) NOT NULL,
|
||||
`inject_date` DATE DEFAULT NULL,
|
||||
`next_date` DATE DEFAULT NULL,
|
||||
`cert_image` VARCHAR(255) DEFAULT NULL,
|
||||
`reminder_enabled` TINYINT DEFAULT 0,
|
||||
`remind_before_days` TINYINT DEFAULT 1,
|
||||
`remark` VARCHAR(255) DEFAULT NULL,
|
||||
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
`update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
`deleted` TINYINT DEFAULT 0,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_pet_id` (`pet_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='疫苗记录';
|
||||
|
||||
-- ------------------------------------------------------------
|
||||
-- 6. 驱虫
|
||||
-- ------------------------------------------------------------
|
||||
DROP TABLE IF EXISTS `pt_deworm`;
|
||||
CREATE TABLE `pt_deworm` (
|
||||
`id` BIGINT NOT NULL AUTO_INCREMENT,
|
||||
`user_id` BIGINT NOT NULL,
|
||||
`pet_id` BIGINT NOT NULL,
|
||||
`drug_name` VARCHAR(128) NOT NULL,
|
||||
`drug_type` TINYINT DEFAULT 1 COMMENT '1体内 2体外',
|
||||
`use_date` DATE DEFAULT NULL,
|
||||
`next_date` DATE DEFAULT NULL,
|
||||
`drug_image` VARCHAR(255) DEFAULT NULL,
|
||||
`reminder_enabled` TINYINT DEFAULT 0,
|
||||
`remind_before_days` TINYINT DEFAULT 1,
|
||||
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
`update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
`deleted` TINYINT DEFAULT 0,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_pet_id` (`pet_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='驱虫记录';
|
||||
|
||||
-- ------------------------------------------------------------
|
||||
-- 7. 就诊记录
|
||||
-- ------------------------------------------------------------
|
||||
DROP TABLE IF EXISTS `pt_visit_record`;
|
||||
CREATE TABLE `pt_visit_record` (
|
||||
`id` BIGINT NOT NULL AUTO_INCREMENT,
|
||||
`user_id` BIGINT NOT NULL,
|
||||
`pet_id` BIGINT NOT NULL,
|
||||
`hospital` VARCHAR(128) DEFAULT NULL,
|
||||
`reason` VARCHAR(255) DEFAULT NULL,
|
||||
`diagnosis` TEXT,
|
||||
`cost` DECIMAL(10,2) DEFAULT NULL,
|
||||
`visit_date` DATE DEFAULT NULL,
|
||||
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
`update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
`deleted` TINYINT DEFAULT 0,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='就诊记录';
|
||||
|
||||
-- ------------------------------------------------------------
|
||||
-- 8. 物资
|
||||
-- ------------------------------------------------------------
|
||||
DROP TABLE IF EXISTS `pt_inventory`;
|
||||
CREATE TABLE `pt_inventory` (
|
||||
`id` BIGINT NOT NULL AUTO_INCREMENT,
|
||||
`user_id` BIGINT NOT NULL,
|
||||
`category` TINYINT DEFAULT 6 COMMENT '1粮 2罐头 3零食 4猫砂 5药品 6其他',
|
||||
`name` VARCHAR(128) NOT NULL,
|
||||
`stock` DECIMAL(10,2) NOT NULL DEFAULT 0,
|
||||
`unit` VARCHAR(16) DEFAULT NULL,
|
||||
`low_stock_threshold` DECIMAL(10,2) DEFAULT 0,
|
||||
`cover_image` VARCHAR(255) DEFAULT NULL,
|
||||
`remark` VARCHAR(255) DEFAULT NULL,
|
||||
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
`update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
`deleted` TINYINT DEFAULT 0,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_user_id` (`user_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='物资库存';
|
||||
|
||||
-- ------------------------------------------------------------
|
||||
-- 9. 物资消耗记录
|
||||
-- ------------------------------------------------------------
|
||||
DROP TABLE IF EXISTS `pt_inventory_log`;
|
||||
CREATE TABLE `pt_inventory_log` (
|
||||
`id` BIGINT NOT NULL AUTO_INCREMENT,
|
||||
`inventory_id` BIGINT NOT NULL,
|
||||
`user_id` BIGINT NOT NULL,
|
||||
`quantity` DECIMAL(10,2) NOT NULL,
|
||||
`remark` VARCHAR(255) DEFAULT NULL,
|
||||
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
`update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
`deleted` TINYINT DEFAULT 0,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='物资消耗';
|
||||
|
||||
-- ------------------------------------------------------------
|
||||
-- 10. 时光机作品
|
||||
-- ------------------------------------------------------------
|
||||
DROP TABLE IF EXISTS `pt_timemachine_work`;
|
||||
CREATE TABLE `pt_timemachine_work` (
|
||||
`id` BIGINT NOT NULL AUTO_INCREMENT,
|
||||
`user_id` BIGINT NOT NULL,
|
||||
`work_type` TINYINT NOT NULL COMMENT '1同角度对比 2AI滤镜 3时光机视频',
|
||||
`pet_id` BIGINT DEFAULT NULL,
|
||||
`title` VARCHAR(128) DEFAULT NULL,
|
||||
`source_urls` JSON DEFAULT NULL,
|
||||
`result_url` VARCHAR(255) DEFAULT NULL,
|
||||
`template_key` VARCHAR(64) DEFAULT NULL,
|
||||
`remark` VARCHAR(255) DEFAULT NULL,
|
||||
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
`update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
`deleted` TINYINT DEFAULT 0,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_user_id` (`user_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='时光机作品';
|
||||
|
||||
-- ------------------------------------------------------------
|
||||
-- 11. 生活信息卡
|
||||
-- ------------------------------------------------------------
|
||||
DROP TABLE IF EXISTS `pt_life_card`;
|
||||
CREATE TABLE `pt_life_card` (
|
||||
`id` BIGINT NOT NULL AUTO_INCREMENT,
|
||||
`category` TINYINT NOT NULL COMMENT '1医院 2餐厅 3公园 4宠物店 5美容',
|
||||
`name` VARCHAR(128) NOT NULL,
|
||||
`cover` VARCHAR(255) DEFAULT NULL,
|
||||
`address` VARCHAR(255) DEFAULT NULL,
|
||||
`phone` VARCHAR(32) DEFAULT NULL,
|
||||
`description` TEXT,
|
||||
`longitude` DECIMAL(10,6) DEFAULT NULL,
|
||||
`latitude` DECIMAL(10,6) DEFAULT NULL,
|
||||
`sort_order` INT DEFAULT 0,
|
||||
`status` TINYINT DEFAULT 1 COMMENT '0下架 1上架',
|
||||
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
`update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
`deleted` TINYINT DEFAULT 0,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_category` (`category`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='生活信息卡';
|
||||
|
||||
-- ------------------------------------------------------------
|
||||
-- 12. 反馈
|
||||
-- ------------------------------------------------------------
|
||||
DROP TABLE IF EXISTS `pt_feedback`;
|
||||
CREATE TABLE `pt_feedback` (
|
||||
`id` BIGINT NOT NULL AUTO_INCREMENT,
|
||||
`user_id` BIGINT NOT NULL,
|
||||
`fb_type` TINYINT DEFAULT 1,
|
||||
`content` TEXT NOT NULL,
|
||||
`contact` VARCHAR(64) DEFAULT NULL,
|
||||
`images` JSON DEFAULT NULL,
|
||||
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
`update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
`deleted` TINYINT DEFAULT 0,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='意见反馈';
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
strict-peer-dependencies=false
|
||||
auto-install-peers=true
|
||||
shamefully-hoist=true
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
import uniHelper from '@uni-helper/eslint-config'
|
||||
|
||||
export default uniHelper()
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="static/logo.svg">
|
||||
<script>
|
||||
const coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)')
|
||||
|| CSS.supports('top: constant(a)'))
|
||||
document.write(
|
||||
`<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0${
|
||||
coverSupport ? ', viewport-fit=cover' : ''}" />`)
|
||||
</script>
|
||||
<title></title>
|
||||
<!--preload-links-->
|
||||
<!--app-context-->
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"><!--app-html--></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
import { defineManifestConfig } from '@uni-helper/vite-plugin-uni-manifest'
|
||||
|
||||
export default defineManifestConfig({
|
||||
'name': '',
|
||||
'appid': '',
|
||||
'description': '',
|
||||
'versionName': '1.0.0',
|
||||
'versionCode': '100',
|
||||
'transformPx': false,
|
||||
/* 5+App特有相关 */
|
||||
'app-plus': {
|
||||
usingComponents: true,
|
||||
nvueStyleCompiler: 'uni-app',
|
||||
compilerVersion: 3,
|
||||
splashscreen: {
|
||||
alwaysShowBeforeRender: true,
|
||||
waiting: true,
|
||||
autoclose: true,
|
||||
delay: 0,
|
||||
},
|
||||
/* 模块配置 */
|
||||
modules: {},
|
||||
/* 应用发布信息 */
|
||||
distribute: {
|
||||
/* android打包配置 */
|
||||
android: {
|
||||
permissions: [
|
||||
'<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>',
|
||||
'<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>',
|
||||
'<uses-permission android:name="android.permission.VIBRATE"/>',
|
||||
'<uses-permission android:name="android.permission.READ_LOGS"/>',
|
||||
'<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>',
|
||||
'<uses-feature android:name="android.hardware.camera.autofocus"/>',
|
||||
'<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>',
|
||||
'<uses-permission android:name="android.permission.CAMERA"/>',
|
||||
'<uses-permission android:name="android.permission.GET_ACCOUNTS"/>',
|
||||
'<uses-permission android:name="android.permission.READ_PHONE_STATE"/>',
|
||||
'<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>',
|
||||
'<uses-permission android:name="android.permission.WAKE_LOCK"/>',
|
||||
'<uses-permission android:name="android.permission.FLASHLIGHT"/>',
|
||||
'<uses-feature android:name="android.hardware.camera"/>',
|
||||
'<uses-permission android:name="android.permission.WRITE_SETTINGS"/>',
|
||||
],
|
||||
},
|
||||
/* ios打包配置 */
|
||||
ios: {},
|
||||
/* SDK配置 */
|
||||
sdkConfigs: {},
|
||||
},
|
||||
},
|
||||
/* 快应用特有相关 */
|
||||
'quickapp': {},
|
||||
/* 小程序特有相关 */
|
||||
'mp-weixin': {
|
||||
appid: '',
|
||||
setting: {
|
||||
urlCheck: false,
|
||||
},
|
||||
usingComponents: true,
|
||||
darkmode: true,
|
||||
themeLocation: 'theme.json',
|
||||
},
|
||||
'mp-alipay': {
|
||||
usingComponents: true,
|
||||
},
|
||||
'mp-baidu': {
|
||||
usingComponents: true,
|
||||
},
|
||||
'mp-toutiao': {
|
||||
usingComponents: true,
|
||||
},
|
||||
'h5': {
|
||||
darkmode: true,
|
||||
themeLocation: 'theme.json',
|
||||
},
|
||||
'uniStatistics': {
|
||||
enable: false,
|
||||
},
|
||||
'vueVersion': '3',
|
||||
})
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
{
|
||||
"name": "uniapp-pawtrace",
|
||||
"type": "module",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "unh dev",
|
||||
"build": "unh build",
|
||||
"about": "unh info",
|
||||
"type-check": "vue-tsc --noEmit",
|
||||
"lint": "eslint .",
|
||||
"lint:fix": "eslint . --fix",
|
||||
"test": "vitest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@dcloudio/uni-app": "3.0.0-5000720260410001",
|
||||
"@dcloudio/uni-app-harmony": "3.0.0-5000720260410001",
|
||||
"@dcloudio/uni-app-plus": "3.0.0-5000720260410001",
|
||||
"@dcloudio/uni-components": "3.0.0-5000720260410001",
|
||||
"@dcloudio/uni-h5": "3.0.0-5000720260410001",
|
||||
"@dcloudio/uni-mp-alipay": "3.0.0-5000720260410001",
|
||||
"@dcloudio/uni-mp-baidu": "3.0.0-5000720260410001",
|
||||
"@dcloudio/uni-mp-harmony": "3.0.0-5000720260410001",
|
||||
"@dcloudio/uni-mp-jd": "3.0.0-5000720260410001",
|
||||
"@dcloudio/uni-mp-kuaishou": "3.0.0-5000720260410001",
|
||||
"@dcloudio/uni-mp-lark": "3.0.0-5000720260410001",
|
||||
"@dcloudio/uni-mp-qq": "3.0.0-5000720260410001",
|
||||
"@dcloudio/uni-mp-toutiao": "3.0.0-5000720260410001",
|
||||
"@dcloudio/uni-mp-weixin": "3.0.0-5000720260410001",
|
||||
"@dcloudio/uni-mp-xhs": "3.0.0-5000720260410001",
|
||||
"@dcloudio/uni-quickapp-webview": "3.0.0-5000720260410001",
|
||||
"@uni-helper/uni-network": "^0.23.1",
|
||||
"@uni-helper/uni-promises": "^0.2.1",
|
||||
"@uni-helper/uni-use": "^0.19.17",
|
||||
"@vueuse/core": "9.13.0",
|
||||
"echarts": "^6.0.0",
|
||||
"pinia": "2.2.4",
|
||||
"uni-echarts": "^2.5.1",
|
||||
"uview-pro": "^0.5.17",
|
||||
"vue": "3.4.21",
|
||||
"vue-i18n": "9.6.2",
|
||||
"vue-router": "4.5.1",
|
||||
"z-paging": "^2.8.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@binbinji/vite-plugin-component-placeholder": "^0.0.15",
|
||||
"@dcloudio/types": "3.4.28",
|
||||
"@dcloudio/uni-automator": "3.0.0-5000720260410001",
|
||||
"@dcloudio/uni-cli-shared": "3.0.0-5000720260410001",
|
||||
"@dcloudio/uni-stacktracey": "3.0.0-5000720260410001",
|
||||
"@dcloudio/vite-plugin-uni": "3.0.0-5000720260410001",
|
||||
"@iconify-json/carbon": "^1.2.20",
|
||||
"@mini-types/alipay": "^3.0.14",
|
||||
"@types/node": "^25.6.0",
|
||||
"@uni-helper/eslint-config": "^0.7.1",
|
||||
"@uni-helper/plugin-uni": "0.1.0",
|
||||
"@uni-helper/unh": "^0.3.1",
|
||||
"@uni-helper/uni-types": "^1.0.0-alpha.8",
|
||||
"@uni-helper/unocss-preset-uni": "^0.2.11",
|
||||
"@uni-helper/vite-plugin-uni-components": "^0.2.10",
|
||||
"@uni-helper/vite-plugin-uni-layouts": "^0.1.11",
|
||||
"@uni-helper/vite-plugin-uni-manifest": "^0.2.12",
|
||||
"@uni-helper/vite-plugin-uni-pages": "^0.3.24",
|
||||
"@uni-helper/vite-plugin-uni-platform": "^0.0.5",
|
||||
"@uni-ku/root": "^1.4.1",
|
||||
"@vue/runtime-core": "3.4.21",
|
||||
"@vue/tsconfig": "^0.9.1",
|
||||
"eslint": "^10.2.1",
|
||||
"miniprogram-api-typings": "^5.1.2",
|
||||
"sass": "1.64.2",
|
||||
"typescript": "5.9.3",
|
||||
"unocss": "66.0.0",
|
||||
"vite": "5.2.8",
|
||||
"vitest": "^4.1.4",
|
||||
"vitest-environment-uniapp": "^0.0.5",
|
||||
"vue-tsc": "^3.2.7"
|
||||
},
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
"unconfig": "7.3.2"
|
||||
}
|
||||
},
|
||||
"overrides": {
|
||||
"unconfig": "7.3.2"
|
||||
},
|
||||
"resolutions": {
|
||||
"unconfig": "7.3.2"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
import { defineUniPages } from '@uni-helper/vite-plugin-uni-pages'
|
||||
|
||||
export default defineUniPages({
|
||||
pages: [],
|
||||
globalStyle: {
|
||||
backgroundColor: '@bgColor',
|
||||
backgroundColorBottom: '@bgColorBottom',
|
||||
backgroundColorTop: '@bgColorTop',
|
||||
backgroundTextStyle: '@bgTxtStyle',
|
||||
navigationBarBackgroundColor: '#000000',
|
||||
navigationBarTextStyle: '@navTxtStyle',
|
||||
navigationBarTitleText: 'Uni Creator',
|
||||
navigationStyle: 'custom',
|
||||
},
|
||||
subPackages: [],
|
||||
})
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue