基于微信小程序开发的宠物领养平台——代码解读

news2025/3/16 10:17:13

项目前端

一、项目的技术架构概况

一句话概括:该项目是基于微信小程序开发的宠物领养平台,采用原生小程序框架进行用户界面的构建,使用 wx.request 进行 API 请求,并通过 getApp() 和本地存储来管理全局状态和用户信息。

一)技术栈主要包括

  • 微信小程序框架:项目的核心框架,用于构建用户界面和实现交互逻辑。
  • JavaScript:主要编程语言,项目中的所有逻辑均使用 JavaScript 实现。
  • JSON:用于配置页面和组件的元信息,如页面路径、组件属性等。
  • WXML:类似 HTML 的模板语言,用于定义页面结构。
  • WXSS:类似 CSS 的样式表语言,用于定义页面和组件的样式。
  • 本地存储:通过 wx.setStorageSyncwx.getStorageSync 管理用户信息和 Token。
  • 网络请求:使用 wx.request 进行 API 请求,与后端进行数据交互。

二)开发环境要求

  • 微信开发者工具:用于开发和调试微信小程序。
  • IDE:推荐使用 IDEA 或 VS Code,配合微信开发者工具进行开发。
  • 依赖管理:通过微信开发者工具自带的依赖管理功能安装和管理项目依赖。
  • 浏览器:Edge 或 Chrome,主要用于调试和查看控制台输出。

三)项目结构分析

└── pet_adoption
    ├── backend
    │   ├── src
    │   │   └── main
    │   │       ├── java
    │   │       ├── resources
    │   │       └── webapp
    │   ├── adopt.sql
    │   └── pom.xml
    └── mp-weixin
        ├── components
        │   └── comment
        ├── pages
        │   ├── adopt
        │   ├── blog
        │   ├── comment
        │   ├── index
        │   ├── message
        │   ├── pet
        │   └── user
        ├── static
        │   └── images
        ├── subpackages
        │   ├── comment
        │   └── guide
        ├── utils
        │   └── imagePathUtils.js
        ├── app.js
        ├── app.json
        ├── config.js
        ├── project.config.json
        └── project.private.config.json
关键目录说明
  • mp-weixin/:微信小程序的主要目录,包含所有前端代码。
    • components/:存放可复用的组件,如评论组件。
    • pages/:存放各个页面的代码,每个页面有对应的 .js.json.wxml.wxss 文件。
    • static/:存放静态资源,如图片。
    • subpackages/:存放子包,用于分包加载优化性能。
    • utils/:存放工具函数,如图片路径处理工具。
    • app.js:应用的入口文件,包含全局状态管理和生命周期方法。
    • app.json:应用的全局配置文件,定义了页面路径和窗口表现。
    • config.js:配置文件,定义了不同环境下的 API 地址。

四)UI组件设计

1. 布局组件
  • TabBar:项目中使用了 TabBar 进行页面导航,定义在 app.json 中,包含首页、宠物列表和个人中心三个主要页面。
  • Swiper:用于实现轮播图组件,如首页的轮播图展示。
  • Navigator:用于页面之间的跳转,常见于各个页面的跳转按钮。
2. 业务组件
  • Form 组件:如 adopt/apply.js 中的领养申请表单,实现了表单验证和数据提交功能。
  • List 组件:如 adopt/list.js 中的领养申请列表,支持分页加载、筛选和刷新功能。
  • Modal 组件:用于显示确认对话框,如领养申请的审核通过和拒绝操作。
  • Image 组件:处理图片路径和预览功能,如 blog/publish.js 中的图片上传和预览。
3. 数据流转
  • Props 和 Events:小程序中通过 data 属性传递数据,通过 bind 方法绑定事件,实现父子组件之间的通信。
  • 全局状态管理:通过 getApp() 获取全局应用实例,使用 globalData 管理全局状态,如用户信息、Token 和未读消息数。
  • 本地存储:使用 wx.setStorageSyncwx.getStorageSync 存储和读取用户信息和 Token,确保用户登录状态的持久化。
  • API 请求:通过 wx.request 发起网络请求,与后端进行数据交互,请求结果通过 Promise 进行处理,确保异步操作的可控性。

五)交互实现

1. 登录与鉴权
  • 登录页面 (user/login.js):提供了微信登录和账号密码登录两种方式,登录成功后保存用户信息和 Token 到本地存储和全局状态中。
  • 鉴权拦截:多个页面在 onLoadonShow 生命周期方法中检查用户是否已登录,若未登录则提示用户登录。
2. 数据加载与刷新
  • 下拉刷新:多个页面实现了下拉刷新功能,如 adopt/list.jsuser/favorite.js,通过 onPullDownRefresh 方法触发数据重新加载。
  • 上拉加载更多:如 adopt/list.jsuser/favorite.js 中的 onReachBottom 方法,用于分页加载更多数据。
  • 轮询机制:如 message/chat.js 中的 startPolling 方法,通过定时器定期检查新消息。
3. 图片处理
  • 图片上传:如 blog/publish.jsblog/edit.js 中的 uploadImages 方法,实现了图片的上传和路径处理。
  • 图片预览:多个页面实现了图片预览功能,如 blog/publish.js 中的 previewImage 方法。
4. 表单验证
  • 表单验证:如 adopt/apply.js 中的 validateForm 方法,对表单数据进行验证,确保提交数据的有效性。
5. 路由管理
  • 页面跳转:使用 wx.navigateTowx.switchTab 实现页面间的跳转,如 index/index.js 中的 navigateToPetDetailnavigateToBlogs 方法。

以上是对项目前端部分的详细分析,涵盖了技术架构、组件设计和交互逻辑等方面的内容。该项目采用了原生微信小程序框架,结合了多种交互方式和状态管理手段,确保了良好的用户体验和高效的开发流程。

二、主要业务功能在代码中的具体实现 & 调用过程

有以下核心业务功能:

  1. 用户认证与权限管理 - 处理用户登录、权限验证和身份管理。
  2. 宠物领养管理 - 包括领养申请、申请详情、申请管理等。
  3. 宠物信息管理 - 允许用户查看、发布、编辑和删除宠物信息。
  4. 故事管理 - 包括发布故事、编辑故事等。
  5. 消息管理 - 用户之间的消息交互。
  6. 收藏管理 - 用户可以收藏宠物和博客。
  7. 用户信息管理 - 用户可以查看和编辑个人信息。

1. 用户认证与权限管理

设计思路

  • 用户通过登录页面提交认证信息。
  • 系统后端验证用户信息,并根据用户角色(普通用户、管理员)提供相应权限。
  • 用户可以通过微信登录或账号密码登录。

代码实现逻辑

  • 用户填写用户名和密码,选择用户类型后提交表单。
  • 后端接收请求,校验用户名和密码的正确性。
  • 登录成功后,将用户信息和token存储到本地,并更新全局数据。
  • 微信登录通过获取code,发送到后端验证,登录成功后同样存储用户信息和token。

调用过程

  • 用户填写登录信息 -> 提交表单 -> 调用后端登录接口 -> 验证用户信息 -> 返回登录结果 -> 存储用户信息和token -> 更新全局数据。

代码片段 (user/login.js 中的登录部分):

async onLogin() {
  if (this.data.loading) return;
  if (!this.data.isAgree) {
    this.setData({ errorMsg: '请先同意用户协议和隐私政策' });
    return;
  }
  if (!this.data.username || !this.data.password) {
    this.setData({ errorMsg: '请输入用户名和密码' });
    return;
  }
  this.setData({ loading: true, errorMsg: '' });
  try {
    const res = await wx.request({
      url: `${app.globalData.baseUrl}/user/login`,
      method: 'POST',
      data: { userName: this.data.username, password: this.data.password }
    });
    if (res.statusCode === 200 && res.data.code === 200) {
      const userData = res.data.data;
      wx.setStorageSync('userInfo', userData.user);
      wx.setStorageSync('token', userData.token);
      wx.setStorageSync('isLogin', true);
      wx.setStorageSync('userId', userData.user.id);
      app.globalData.userInfo = userData.user;
      app.globalData.token = userData.token;
      app.globalData.userId = userData.user.id;
      wx.showToast({ title: '登录成功', icon: 'success' });
      setTimeout(() => {
        const pages = getCurrentPages();
        if (pages.length > 1) {
          wx.navigateBack();
        } else {
          wx.switchTab({ url: '/pages/index/index' });
        }
      }, 1500);
    } else {
      this.setData({ errorMsg: res.data.message || '登录失败' });
    }
  } catch (error) {
    this.setData({ errorMsg: error.errMsg || '登录失败,请重试' });
  } finally {
    this.setData({ loading: false });
  }
}

2. 宠物领养管理

设计思路

  • 用户可以申请领养宠物,填写相关信息。
  • 管理员或宠物发布者可以审核领养申请。
  • 用户可以查看领养申请的详细信息。

代码实现逻辑

  • 用户填写领养申请表单,提交后发送至后端。
  • 后端验证表单信息,并存储申请记录。
  • 管理员或宠物发布者可以在列表中查看申请,进行审核操作。
  • 审核通过或拒绝后,更新申请状态,并通知相关用户。

调用过程

  • 用户填写领养申请表单 -> 提交表单 -> 调用后端接口 -> 存储申请记录 -> 管理员或宠物发布者查看申请 -> 审核通过或拒绝 -> 更新申请状态。

代码片段 (adopt/apply.js 中的表单提交部分):

submitForm(e) {
  const formData = e.detail.value;
  if (!this.validateForm(formData)) return;
  this.setData({ submitting: true });
  const requestData = {
    petId: this.data.petId,
    remark: JSON.stringify({
      name: formData.name,
      phone: formData.phone,
      wechat: formData.wechat,
      address: formData.address,
      houseType: this.data.houseTypes[formData.houseTypeIndex],
      isRent: formData.isRent,
      landlordAgree: formData.landlordAgree,
      familyMembers: formData.familyMembers,
      familyAgree: formData.familyAgree,
      hasPetExperience: formData.hasPetExperience,
      description: formData.description
    })
  };
  wx.request({
    url: `${app.globalData.baseUrl}/adopt/apply`,
    method: 'POST',
    header: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${wx.getStorageSync('token')}`
    },
    data: requestData,
    success: (res) => {
      if (res.data.code === 200) {
        wx.showToast({ title: '申请提交成功', icon: 'success' });
        setTimeout(() => {
          const pages = getCurrentPages();
          const prevPage = pages[pages.length - 2];
          if (prevPage && prevPage.loadPetDetail) {
            prevPage.loadPetDetail(prevPage.data.pet.id);
          }
          wx.navigateBack();
        }, 2000);
      } else {
        wx.showToast({ title: res.data.message || '提交失败,请重试', icon: 'error' });
      }
    },
    complete: () => {
      this.setData({ submitting: false });
    }
  });
}

3. 宠物信息管理

设计思路

  • 用户可以查看宠物列表、发布新宠物、编辑已有的宠物信息。
  • 宠物信息包括图片、描述等。

代码实现逻辑

  • 前端展示宠物列表,用户可以选择查看、发布或编辑宠物。
  • 发布和编辑宠物时,用户可以上传图片并填写相关信息。
  • 提交后,后端验证并存储宠物信息。

调用过程

  • 用户查看宠物列表 -> 点击发布或编辑 -> 填写宠物信息 -> 提交表单 -> 调用后端接口 -> 存储宠物信息。

代码片段 (pet/publish.js 中的宠物发布部分):

async submitForm(e) {
  try {
    const { title, content } = this.data.formData;
    if (!title.trim()) {
      wx.showToast({ title: '请输入故事标题', icon: 'none' });
      return;
    }
    if (!content.trim()) {
      wx.showToast({ title: '请输入故事内容', icon: 'none' });
      return;
    }
    wx.showLoading({ title: '保存中...', mask: true });
    const images = await this.uploadImages();
    const coverImage = images.join(',');
    const petData = {
      title: title.trim(),
      content: content.trim(),
      coverImage,
      tags: this.data.selectedTags.join(',')
    };
    const res = await wx.request({
      url: `${app.globalData.baseUrl}/pets`,
      method: 'POST',
      data: petData,
      header: {
        'Authorization': wx.getStorageSync('token'),
        'Content-Type': 'application/json'
      }
    });
    if (res.statusCode === 200 && res.data.code === 200) {
      wx.hideLoading();
      wx.showToast({ title: '保存成功', icon: 'success' });
      setTimeout(() => {
        const pages = getCurrentPages();
        const detailPage = pages[pages.length - 2];
        if (detailPage && detailPage.loadPetDetail) {
          detailPage.loadPetDetail();
        }
        wx.navigateBack();
      }, 1500);
    } else {
      throw new Error(res.data.message || '保存失败');
    }
  } catch (error) {
    wx.hideLoading();
    wx.showToast({ title: error.message || '保存失败', icon: 'none' });
  }
}

4. 故事管理

设计思路

  • 用户可以发布新的故事,编辑已有的故事。
  • 故事包括标题、内容、封面图片和标签。

代码实现逻辑

  • 用户填写故事标题、内容、选择封面图片和标签。
  • 提交后,后端验证并存储故事信息。

调用过程

  • 用户填写故事信息 -> 提交表单 -> 调用后端接口 -> 存储故事信息。

代码片段 (blog/publish.js 中的故事发布部分):

submitForm(e) {
  try {
    const { title, content } = this.data.formData;
    if (!title.trim()) {
      wx.showToast({ title: '请输入故事标题', icon: 'none' });
      return;
    }
    if (!content.trim()) {
      wx.showToast({ title: '请输入故事内容', icon: 'none' });
      return;
    }
    if (this.data.tempImages.length === 0) {
      wx.showToast({ title: '请至少上传一张图片', icon: 'none' });
      return;
    }
    wx.showLoading({ title: '正在发布...', mask: true });
    this.uploadImages()
      .then(uploadedImages => {
        const submitData = {
          title: title.trim(),
          content: content.trim(),
          coverImage: uploadedImages.join(','),
          tags: this.data.selectedTags.join(','),
          status: 1
        };
        return wx.request({
          url: `${app.globalData.baseUrl}/blogs`,
          method: 'POST',
          data: submitData,
          header: {
            'Authorization': `Bearer ${wx.getStorageSync('token').replace('Bearer ', '')}`,
            'Content-Type': 'application/json'
          }
        });
      })
      .then(res => {
        if (res.statusCode === 200 && res.data.code === 200) {
          wx.hideLoading();
          wx.showToast({ title: '发布成功', icon: 'success', duration: 2000 });
          setTimeout(() => {
            wx.navigateBack();
          }, 2000);
        } else {
          throw new Error(res.data.message || '发布失败');
        }
      })
      .catch(err => {
        wx.hideLoading();
        wx.showToast({ title: typeof err === 'string' ? err : (err.message || '发布失败'), icon: 'none', duration: 2000 });
      });
  } catch (error) {
    wx.hideLoading();
    wx.showToast({ title: error.message || '发布失败', icon: 'none', duration: 2000 });
  }
}

5. 消息管理

设计思路

  • 用户之间可以进行消息交流。
  • 消息包括文本、图片等内容。

代码实现逻辑

  • 用户发送消息,消息存储到数据库。
  • 消息列表页面展示用户之间的消息,并支持加载更多。
  • 消息实时轮询,保证用户及时看到新消息。

调用过程

  • 用户输入消息内容 -> 点击发送 -> 调用后端接口 -> 存储消息 -> 更新消息列表 -> 实时轮询新消息。

代码片段 (message/chat.js 中的消息发送部分):

async sendMessage() {
  const content = this.data.inputContent.trim();
  if (!content) return;
  const token = wx.getStorageSync('token');
  if (!token) return;
  try {
    const requestData = {
      sessionId: this.data.sessionId,
      senderId: this.data.userInfo.id,
      content
    };
    const res = await wx.request({
      url: `${app.globalData.baseUrl}/messages/send`,
      method: 'POST',
      header: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },
      data: requestData
    });
    if (res.statusCode === 200 && res.data.code === 200) {
      this.setData({ inputContent: '' });
      const newMessage = res.data.data;
      newMessage.createTimeStr = this.formatTime(newMessage.createTime);
      newMessage.senderAvatar = newMessage.sender?.pic ? getImageUrl(newMessage.sender.pic) : '/static/images/icon/default-avatar.png';
      const messages = [...this.data.messages, newMessage];
      this.setData({ messages, scrollIntoView: `msg-${newMessage.id}` });
    } else {
      wx.showToast({ title: res.data.message || '发送失败', icon: 'none' });
    }
  } catch (error) {
    wx.showToast({ title: error.message || '网络错误', icon: 'none' });
  }
}

6. 收藏管理

设计思路

  • 用户可以收藏感兴趣的宠物和博客。
  • 用户可以查看已收藏的宠物和博客,并进行取消收藏操作。

代码实现逻辑

  • 用户点击收藏按钮,后端记录收藏信息。
  • 用户可以在收藏页面查看已收藏的内容,并进行取消收藏操作。

调用过程

  • 用户点击收藏按钮 -> 调用后端接口 -> 记录收藏信息 -> 用户查看收藏列表 -> 取消收藏 -> 调用取消收藏接口。

代码片段 (user/favorite.js 中的收藏部分):

async onFavoriteTap(e) {
  const { id, type } = e.currentTarget.dataset;
  const token = wx.getStorageSync('token');
  if (!token) return;
  try {
    const url = type === 'pet' ? `${app.globalData.baseUrl}/favorites/remove/${id}` : `${app.globalData.baseUrl}/blog/favorites/remove`;
    const data = type === 'pet' ? null : { blogId: id };
    const method = 'POST';
    const res = await wx.request({
      url,
      method,
      header: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },
      data
    });
    if (res.data.code === 200) {
      const listKey = type === 'pet' ? 'pets' : 'blogs';
      const list = this.data[listKey].filter(item => item.id !== id);
      this.setData({ [listKey]: list });
      wx.showToast({ title: '已取消收藏', icon: 'success' });
    } else {
      throw new Error(res.data.message || '操作失败');
    }
  } catch (error) {
    wx.showToast({ title: error.message || '操作失败', icon: 'none' });
  }
}

7. 用户信息管理

设计思路

  • 用户可以查看和编辑个人信息。
  • 用户可以上传头像、修改昵称、真实姓名、电话号码等。

代码实现逻辑

  • 用户选择头像或填写个人信息。
  • 提交后,后端验证并更新用户信息。

调用过程

  • 用户选择头像或填写信息 -> 提交表单 -> 调用后端接口 -> 更新用户信息。

代码片段 (user/profile.js 中的用户信息保存部分):

async onSave() {
  try {
    if (this.data.userInfo.telephone && !/^1[3-9]\d{9}$/.test(this.data.userInfo.telephone)) {
      wx.showToast({ title: '手机号码格式不正确', icon: 'none' });
      return;
    }
    if (this.data.userInfo.email && !/^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$/.test(this.data.userInfo.email)) {
      wx.showToast({ title: '邮箱格式不正确', icon: 'none' });
      return;
    }
    wx.showLoading({ title: '保存中...', mask: true });
    const updateData = {
      id: this.data.userInfo.id,
      ...this.data.userInfo
    };
    const res = await wx.request({
      url: `${app.globalData.baseUrl}/user/update`,
      method: 'POST',
      data: updateData,
      header: {
        'Authorization': `Bearer ${wx.getStorageSync('token')}`,
        'Content-Type': 'application/json'
      }
    });
    if (res.data.code === 200) {
      wx.setStorageSync('userInfo', this.data.userInfo);
      app.globalData.userInfo = this.data.userInfo;
      wx.showToast({ title: '保存成功', icon: 'success' });
      setTimeout(() => {
        wx.navigateBack();
      }, 1500);
    } else {
      throw new Error(res.data.message || '保存失败');
    }
  } catch (error) {
    wx.showToast({ title: error.message || '保存失败', icon: 'none' });
  } finally {
    wx.hideLoading();
  }
}

8. 首页展示与导航

设计思路

  • 首页展示轮播图、推荐宠物和最新故事,吸引用户关注。
  • 提供导航功能,用户可以跳转到不同的页面。

代码实现逻辑

  • 加载轮播图、推荐宠物和最新故事的数据。
  • 处理图片路径,确保显示正确的图片。
  • 提供跳转功能,如跳转到宠物详情、博客详情、发布宠物等。

调用过程

  • 页面加载 -> 初始化数据 -> 加载轮播图、推荐宠物和最新故事 -> 展示数据 -> 用户点击跳转 -> 导航到对应页面。

代码片段 (index/index.js 中的首页加载部分):

initData: function() {
  this.loadBanners();
  this.loadRecommendPets();
  this.loadLatestBlogs();
}

loadBanners: function() {
  const that = this;
  wx.request({
    url: `${getApp().globalData.baseUrl}/banners`,
    success: function(res) {
      if (res.data.code === 200) {
        const banners = res.data.data.map(banner => {
          let imageUrl = banner.imageUrl;
          if (imageUrl && imageUrl.includes('localhost')) {
            const serverUrl = config.baseUrl.match(/^(https?:\/\/[^\/]+)/)[0];
            imageUrl = imageUrl.replace(/http:\/\/localhost:8080/, serverUrl);
          }
          return { ...banner, imageUrl };
        });
        that.setData({ bannerList: banners });
      }
    },
    complete: function() {
      that.setData({ loading: false });
    }
  });
}

9. 忘记密码

设计思路

  • 用户可以通过手机号找回密码。
  • 系统验证用户信息并重置密码。

代码实现逻辑

  • 用户输入用户名、手机号和新密码。
  • 系统验证手机号格式和密码一致性。
  • 发送重置密码请求到后端。

调用过程

  • 用户输入信息 -> 提交表单 -> 验证信息 -> 调用后端接口 -> 重置密码 -> 返回结果。

代码片段 (user/forgot-password.js 中的重置密码部分):

async onResetPassword() {
  if (this.data.loading) return;
  if (!this.data.userName || !this.data.telephone) {
    this.setData({ errorMsg: '请输入用户名和手机号' });
    return;
  }
  if (!/^1[3-9]\d{9}$/.test(this.data.telephone)) {
    this.setData({ errorMsg: '请输入正确的手机号' });
    return;
  }
  if (!this.data.password || !this.data.confirmPassword) {
    this.setData({ errorMsg: '请输入新密码' });
    return;
  }
  if (this.data.password !== this.data.confirmPassword) {
    this.setData({ errorMsg: '两次输入的密码不一致' });
    return;
  }
  if (this.data.password.length < 6) {
    this.setData({ errorMsg: '密码长度不能少于6位' });
    return;
  }
  this.setData({ loading: true, errorMsg: '' });
  try {
    const res = await wx.request({
      url: `${app.globalData.baseUrl}/user/reset-password`,
      method: 'POST',
      header: { 'Content-Type': 'application/json' },
      data: { userName: this.data.userName, telephone: this.data.telephone, password: this.data.password }
    });
    if (res.statusCode === 200 && res.data.code === 200) {
      wx.showToast({ title: '密码重置成功', icon: 'success' });
      setTimeout(() => {
        wx.navigateBack();
      }, 1500);
    } else {
      throw new Error(res.data.message || '未知错误');
    }
  } catch (error) {
    this.setData({ errorMsg: error.message || '重置密码失败,请重试' });
  } finally {
    this.setData({ loading: false });
  }
}

10. 用户设置

设计思路

  • 用户可以修改通知设置、切换深色模式、修改密码、清除缓存和退出登录。
  • 提供关于我们的页面链接。

代码实现逻辑

  • 用户选择设置项,如修改通知设置、切换深色模式等。
  • 修改密码时,用户输入旧密码和新密码,后端验证并更新密码。
  • 清除缓存时,删除本地存储的所有数据。
  • 退出登录时,清除用户信息并返回登录页。

调用过程

  • 用户选择设置项 -> 修改设置 -> 更新本地存储或调用后端接口 -> 返回结果。

代码片段 (user/settings.js 中的修改密码部分):

changePassword: function() {
  wx.navigateTo({
    url: '/pages/user/change-password'
  });
}

logout: function() {
  const that = this;
  wx.showModal({
    title: '提示',
    content: '确定要退出登录吗?',
    success: function(res) {
      if (res.confirm) {
        wx.request({
          url: `${getApp().globalData.baseUrl}/user/logout`,
          method: 'POST',
          header: { 'Authorization': wx.getStorageSync('token') },
          success: function(res) {
            if (res.data.code === 0) {
              wx.removeStorageSync('userInfo');
              wx.removeStorageSync('token');
              that.setData({ userInfo: null });
              wx.showToast({ title: '已退出登录', icon: 'success' });
              setTimeout(() => {
                wx.reLaunch({ url: '/pages/user/index' });
              }, 1500);
            } else {
              wx.showToast({ title: res.data.message || '退出失败', icon: 'none' });
            }
          },
          fail: function() {
            wx.showToast({ title: '网络错误', icon: 'none' });
          }
        });
      }
    }
  });
}

11. 宠物详情与编辑

设计思路

  • 用户可以查看宠物的详细信息,包括图片、描述等。
  • 用户可以编辑已发布的宠物信息。

代码实现逻辑

  • 加载宠物详情数据,处理图片路径。
  • 编辑宠物时,用户可以修改图片、描述等信息。
  • 提交编辑内容,后端验证并更新宠物信息。

调用过程

  • 用户查看宠物详情 -> 加载数据 -> 用户点击编辑 -> 修改信息 -> 提交表单 -> 更新宠物信息。

代码片段 (pet/detail.js 中的宠物详情加载部分):

loadPetDetail() {
  const that = this;
  wx.request({
    url: `${app.globalData.baseUrl}/pets/${this.data.petId}`,
    success: function(res) {
      if (res.statusCode === 200 && res.data.code === 200) {
        const pet = res.data.data;
        let images = [];
        let mainImage = '/static/images/pets/default.jpg';
        if (pet.pic) {
          images = pet.pic.split(',').map(pic => {
            pic = pic.trim();
            if (!pic.startsWith('http') && !pic.startsWith('https')) {
              if (pic.startsWith('/adopt')) {
                pic = pic.substring(6);
              }
              if (!pic.startsWith('/')) {
                pic = '/' + pic;
              }
              return `${app.globalData.baseUrl}${pic}`;
            }
            return pic;
          });
          mainImage = images[0];
        }
        that.setData({ pet, images, mainImage });
      } else {
        wx.showToast({ title: res.data.message || '加载失败', icon: 'none' });
      }
    }
  });
}

12. 领养申请管理

设计思路

  • 用户可以查看和管理领养申请,包括通过和拒绝申请。
  • 管理员可以查看所有领养申请,并进行批量审核。

代码实现逻辑

  • 加载领养申请列表,处理图片路径。
  • 管理员或宠物发布者可以审核申请,填写拒绝理由。
  • 审核通过或拒绝后,更新申请状态,并通知相关用户。

调用过程

  • 用户查看领养申请列表 -> 加载数据 -> 用户点击审核 -> 填写拒绝理由 -> 提交审核 -> 更新申请状态。

代码片段 (adopt/manage.js 中的领养申请审核部分):

async updateAdoptStatus(e) {
  const { id, status } = e.currentTarget.dataset;
  try {
    const res = await wx.request({
      url: `${app.globalData.baseUrl}/adopt/status`,
      method: 'PUT',
      data: { id, status },
      header: { 'Authorization': `Bearer ${wx.getStorageSync('token')}`, 'Content-Type': 'application/json' }
    });
    if (res.data.code === 200) {
      wx.showToast({ title: '更新成功', icon: 'success' });
      this.loadAdoptions(true);
    } else {
      throw new Error(res.data.message || '更新失败');
    }
  } catch (error) {
    wx.showToast({ title: error.message || '更新失败', icon: 'none' });
  }
}

13. 用户收藏管理

设计思路

  • 用户可以收藏宠物和博客。
  • 用户可以在收藏页面查看和取消收藏。

代码实现逻辑

  • 加载用户收藏的宠物和博客列表。
  • 用户点击取消收藏按钮,发送请求到后端。
  • 成功取消收藏后,更新本地收藏列表。

调用过程

  • 用户进入收藏页面 -> 加载收藏列表 -> 用户点击取消收藏 -> 发送请求 -> 更新收藏列表。

代码片段 (user/favorite.js 中的取消收藏部分):

async onFavoriteTap(e) {
  const { id, type } = e.currentTarget.dataset;
  const token = wx.getStorageSync('token');
  if (!token) return;
  try {
    const url = type === 'pet' ? `${app.globalData.baseUrl}/favorites/remove/${id}` : `${app.globalData.baseUrl}/blog/favorites/remove`;
    const data = type === 'pet' ? null : { blogId: id };
    const method = 'POST';
    const res = await wx.request({
      url,
      method,
      header: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },
      data
    });
    if (res.data.code === 200) {
      const listKey = type === 'pet' ? 'pets' : 'blogs';
      const list = this.data[listKey].filter(item => item.id !== id);
      this.setData({ [listKey]: list });
      wx.showToast({ title: '已取消收藏', icon: 'success' });
    } else {
      throw new Error(res.data.message || '操作失败');
    }
  } catch (error) {
    wx.showToast({ title: error.message || '操作失败', icon: 'none' });
  }
}

14. 用户注册

设计思路

  • 新用户可以通过填写注册信息来创建账户。
  • 注册信息包括用户名、密码、真实姓名、手机号等。

代码实现逻辑

  • 用户填写注册信息,提交表单。
  • 后端验证注册信息的合法性,并创建新用户。
  • 注册成功后,返回登录页。

调用过程

  • 用户填写注册信息 -> 提交表单 -> 验证信息 -> 调用后端接口 -> 创建新用户 -> 返回登录页。

代码片段 (user/register.js 中的注册部分):

async onRegister() {
  if (!this.validateForm()) return;
  try {
    const res = await wx.request({
      url: `${app.globalData.baseUrl}/user/register`,
      method: 'POST',
      data: {
        userName: this.data.username,
        password: this.data.password,
        role: 'USER',
        status: 1,
        ...(this.data.realName ? { realName: this.data.realName } : {}),
        ...(this.data.telephone ? { telephone: this.data.telephone } : {}),
        ...(this.data.email ? { email: this.data.email } : {}),
        ...(this.data.sex ? { sex: this.data.sex } : {}),
        ...(this.data.birthday ? { birthday: this.data.birthday } : {}),
        ...(this.data.description ? { description: this.data.description } : {})
      },
      header: { 'Content-Type': 'application/json' }
    });
    if (res.statusCode === 200 && res.data.code === 200) {
      wx.showToast({ title: '注册成功', icon: 'success' });
      setTimeout(() => {
        wx.navigateBack();
      }, 1500);
    } else {
      throw new Error(res.data.message || '未知错误');
    }
  } catch (error) {
    wx.showToast({ title: error.message || '注册失败,请重试', icon: 'none' });
  } finally {
    wx.hideLoading();
  }
}

15. 用户评论管理

设计思路

  • 用户可以查看和删除自己的评论。
  • 用户可以对宠物或博客发表评论。

代码实现逻辑

  • 加载用户评论列表,处理图片路径。
  • 用户点击删除按钮,发送请求到后端。
  • 成功删除评论后,更新本地评论列表。

调用过程

  • 用户进入评论页面 -> 加载评论列表 -> 用户点击删除 -> 发送请求 -> 更新评论列表。

代码片段 (user/comments.js 中的删除评论部分):

deleteComment: function(e) {
  const commentId = e.currentTarget.dataset.id;
  const that = this;
  wx.showModal({
    title: '提示',
    content: '确定要删除这条评论吗?',
    success: function(res) {
      if (res.confirm) {
        wx.request({
          url: `${getApp().globalData.baseUrl}/comments/${commentId}`,
          method: 'DELETE',
          header: { 'Authorization': wx.getStorageSync('token') },
          success: function(res) {
            if (res.data.code === 0) {
              wx.showToast({ title: '删除成功', icon: 'success' });
              that.setData({ currentPage: 1, hasMore: true, commentList: [] }, () => {
                that.loadComments();
              });
            } else {
              wx.showToast({ title: res.data.message || '删除失败', icon: 'none' });
            }
          }
        });
      }
    }
  });
}

16. 消息轮询与通知

设计思路

  • 实现实时消息轮询,确保用户能及时看到新消息。
  • 消息轮询定时器每30秒检查一次未读消息。

代码实现逻辑

  • 在应用启动和前台显示时,启动消息轮询定时器。
  • 每30秒调用一次未读消息接口,检查是否有新消息。
  • 如果有新消息,更新全局未读消息计数,并显示小红点。

调用过程

  • 应用启动或进入前台 -> 启动消息轮询 -> 定时检查未读消息 -> 更新未读消息计数 -> 显示小红点。

代码片段 (mp-weixin/app.js 中的消息轮询部分):

startCheckingMessages() {
  console.log('开始检查未读消息');
  this.stopCheckingMessages();
  this.checkUnreadMessages();
  this.globalData.messagePollingTimer = setInterval(() => {
    this.checkUnreadMessages();
  }, 30000);
}

async checkUnreadMessages() {
  try {
    const token = this.globalData.token;
    const userInfo = this.globalData.userInfo;
    if (!token || !userInfo) return;
    const res = await wx.request({
      url: `${this.globalData.baseUrl}/messages/unread/count`,
      method: 'GET',
      header: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },
      data: { userId: userInfo.id }
    });
    if (res.statusCode === 200 && res.data.code === 200) {
      const unreadCount = res.data.data || 0;
      this.globalData.unreadMessageCount = unreadCount;
      if (unreadCount > 0) {
        wx.showTabBarRedDot({ index: 3 });
      } else {
        wx.hideTabBarRedDot({ index: 3 });
      }
    }
  } catch (error) {
    console.error('检查未读消息失败:', error);
  }
}

17. 图片资源路径管理

设计思路

  • 统一处理图片路径,确保图片路径的正确性和完整性。
  • 提供工具函数,方便在各个页面调用。

代码实现逻辑

  • 检查图片路径是否为完整的URL,如果不是则拼接基础URL。
  • 如果是静态资源路径,直接返回。
  • 如果路径以 /adopt 开头,移除它并拼接基础URL。

调用过程

  • 各页面调用工具函数 -> 检查图片路径 -> 返回完整的图片URL。

代码片段 (utils/imagePathUtils.js 中的图片路径处理部分):

const getImageUrl = (path) => {
  if (!path) return '/static/images/tabbar/user.png';
  if (path.startsWith('http://') || path.startsWith('https://')) return path;
  if (path.startsWith('/static/')) return path;
  if (path.startsWith('/')) return `${app.globalData.baseUrl}${path}`;
  return `${app.globalData.baseUrl}/${path}`;
};

module.exports = { getImageUrl };

以上是对整个项目的前端业务功能的具体实现和调用过程的详细分析。

每个功能模块都遵循了清晰的设计思路、详细的代码实现逻辑和明确的调用过程,确保系统的稳定性和用户体验。

三、可能的答辩问题

一)如何处理用户登录状态?

解答:项目通过 wx.getStorageSync 方法从本地存储中获取用户的 tokenuserInfo,并在每次页面加载时进行验证。如果用户未登录或登录状态不完整,会提示用户先登录,并跳转到登录页面。此外,在 app.js 中,应用启动时也会检查登录状态,并通过 clearLoginState 方法清除不完整的登录状态。

代码片段

// 文件: adopt/apply.js
if (!token || !userInfo || !isLogin) {
  wx.showModal({
    title: '提示',
    content: '请先登录后再申请领养',
    success(res) {
      if (res.confirm) {
        wx.navigateTo({
          url: '/pages/user/login'
        });
      } else {
        wx.navigateBack();
      }
    }
  });
  return;
}

二)项目是如何处理图片路径的?

解答:项目中使用了 utils/imagePathUtils.js 中的 getImageUrl 方法来处理图片路径。该方法会根据图片路径的不同情况(是否为完整 URL、是否为静态资源路径等),返回完整的图片 URL。同时,在多个地方(如 index/index.jsblog/edit.js)都会调用此方法来处理图片路径。

代码片段

// 文件: utils/imagePathUtils.js
const getImageUrl = (path) => {
  if (!path) {
    console.error('图片路径为空');
    return '/static/images/tabbar/user.png';
  }
  if (path.startsWith('http://') || path.startsWith('https://')) {
    return path;
  }
  if (path.startsWith('/static/')) {
    return path;
  }
  if (path.startsWith('/')) {
    return `${app.globalData.baseUrl}${path}`;
  } else {
    return `${app.globalData.baseUrl}/${path}`;
  }
}

三)项目是如何实现表单验证的?

解答:项目在多个页面中实现了表单验证,例如在 adopt/apply.js 中,通过正则表达式验证手机号格式,并检查家庭成员数是否为有效的数字。此外,还提供了友好的提示信息,确保用户输入符合要求。

代码片段

// 文件: adopt/apply.js
validateForm(formData) {
  const phoneReg = /^1[3-9]\d{9}$/;
  if (!formData.phone || !phoneReg.test(formData.phone)) {
    wx.showToast({
      title: '请输入正确的手机号',
      icon: 'none'
    });
    return false;
  }
  if (formData.familyMembers && (isNaN(formData.familyMembers) || formData.familyMembers < 1)) {
    wx.showToast({
      title: '请输入正确的家庭成员数',
      icon: 'none'
    });
    return false;
  }
  return true;
}

四)如何处理与后端的数据交互?

解答:项目通过 wx.request 方法与后端进行数据交互。每次请求都携带了 Authorization 头部,确保请求的安全性。此外,项目还使用了 Promise 包装 wx.request,以便更好地处理异步操作。例如,在 adopt/apply.js 中,提交表单时会发送 POST 请求给后端,并根据响应结果进行相应处理。

代码片段

// 文件: adopt/apply.js
wx.request({
  url: `${app.globalData.baseUrl}/adopt/apply`,
  method: 'POST',
  header: {
    'Content-Type': 'application/json',
    'Authorization': `Bearer ${token}`
  },
  data: requestData,
  success: (res) => {
    if (res.data && (res.data.code === 0 || res.data.code === 200)) {
      wx.showToast({
        title: '申请提交成功',
        icon: 'success',
        duration: 2000
      });
    } else {
      wx.showToast({
        title: res.data?.message || '提交失败,请重试',
        icon: 'error'
      });
    }
  },
  fail: (error) => {
    wx.showToast({
      title: '网络错误,请重试',
      icon: 'error'
    });
  }
});

五)项目是如何处理轮播图的?

解答:项目在 index/index.js 中实现了轮播图的加载和展示。首先通过 wx.request 请求获取轮播图数据,然后根据返回的数据动态生成轮播图项。为了确保图片路径的正确性,还会对图片路径进行处理,确保路径是完整的 URL。

代码片段

// 文件: index/index.js
loadBanners: function() {
  wx.request({
    url: `${getApp().globalData.baseUrl}/banners`,
    success: function(res) {
      if (res.data.code === 200) {
        const banners = res.data.data.map(banner => {
          let imageUrl = banner.imageUrl;
          if (imageUrl && imageUrl.includes('localhost')) {
            const serverUrl = config.baseUrl.match(/^(https?:\/\/[^\/]+)/)[0];
            imageUrl = imageUrl.replace(/http:\/\/localhost:8080/, serverUrl);
          }
          return {
            ...banner,
            imageUrl
          };
        });
        that.setData({ bannerList: banners });
      }
    }
  });
}

六)项目是如何实现分页加载的?

解答:项目在多个页面(如 adopt/list.jsuser/favorite.js)中实现了分页加载。通过 wx.request 请求分页数据,并根据返回的 totallist 动态更新页面数据。每次加载更多数据时,会增加 pageNum,并通过 hasMore 判断是否还有更多数据可以加载。

代码片段

// 文件: adopt/list.js
async loadAdopts(refresh = false) {
  if (refresh) {
    this.setData({
      pageNum: 1,
      hasMore: true
    });
  }
  if (!this.data.hasMore) return;
  try {
    const token = wx.getStorageSync('token');
    const res = await new Promise((resolve, reject) => {
      wx.request({
        url: `${app.globalData.baseUrl}/adopt/list`,
        method: 'GET',
        data: {
          page: this.data.pageNum,
          size: this.data.pageSize
        },
        header: {
          'Authorization': `Bearer ${token}`
        },
        success: resolve,
        fail: reject
      });
    });
    if (res.data.code === 200) {
      const list = res.data.data.list || [];
      const total = res.data.data.total || 0;
      this.setData({
        adopts: refresh ? list : [...this.data.adopts, ...list],
        hasMore: this.data.pageNum * this.data.pageSize < total,
        pageNum: this.data.pageNum + 1
      });
    }
  } catch (error) {
    console.error('[领养申请] 加载列表出错:', error);
  }
}

七)项目是如何处理用户权限的?

解答:项目通过 wx.getStorageSync 获取用户信息,并在页面加载时检查用户角色(如 isAdmin)。在 adopt/list.js 中,只有管理员或宠物发布者才能审核领养申请,且不能审核自己的申请。此外,通过 checkLogin 方法确保用户已登录。

代码片段

// 文件: adopt/list.js
const canApprove = (
  this.data.isAdmin || 
  (this.data.isPetPublisher && 
   item.pet && 
   item.pet.rescuerId === this.data.userId &&
   item.userId !== this.data.userId)
) && item.state === 1;

this.setData({
  isAdmin: userInfo && userInfo.role === 'ADMIN',
  userId: userInfo ? userInfo.id : null
});

八)项目是如何处理图片上传的?

解答:项目在 blog/publish.jsblog/edit.js 中实现了图片上传功能。通过 wx.chooseImage 选择图片,然后使用 wx.uploadFile 方法上传图片到服务器。上传成功后,返回的图片路径会被处理成完整的 URL 并保存到 tempImages 中,供后续使用。

代码片段

// 文件: blog/publish.js
chooseImage() {
  const that = this;
  const remainCount = 9 - this.data.tempImages.length;
  if (remainCount <= 0) {
    wx.showToast({
      title: '最多上传9张图片',
      icon: 'none'
    });
    return;
  }
  wx.chooseImage({
    count: remainCount,
    sizeType: ['compressed'],
    sourceType: ['album', 'camera'],
    success: function(res) {
      const newTempImages = [...that.data.tempImages, ...res.tempFilePaths];
      that.setData({
        tempImages: newTempImages
      });
    }
  });
}

九)项目是如何处理消息轮询的?

解答:项目在 message/chat.js 中实现了消息轮询功能。通过 setInterval 每隔 5 秒检查一次新消息。每次轮询时,会发送 GET 请求获取最新的消息列表,并将其与现有消息列表进行对比,过滤出新消息并更新页面。

代码片段

// 文件: message/chat.js
startPolling() {
  this.stopPolling();
  this.data.pollingTimer = setInterval(() => {
    if (this.data.sessionId) {
      this.checkNewMessages();
    }
  }, 5000);
}

十)项目是如何处理用户收藏的?

解答:项目在 user/favorites.js 中实现了用户收藏功能。通过 wx.request 请求获取用户的收藏列表,并根据不同的收藏类型(宠物或博客)分别处理数据。同时,提供了一键取消收藏和单个取消收藏的功能,确保用户可以方便地管理自己的收藏。

代码片段

// 文件: user/favorites.js
onFavoriteTap(e) {
  const { id, type } = e.currentTarget.dataset;
  const url = type === 'pet'
    ? `${app.globalData.baseUrl}/favorites/remove/${id}`
    : `${app.globalData.baseUrl}/blog/favorites/remove`;
  const data = type === 'pet' ? null : { blogId: id };
  const method = type === 'pet' ? 'POST' : 'POST';
  const res = await new Promise((resolve, reject) => {
    wx.request({
      url,
      method,
      header: {
        'Authorization': `Bearer ${wx.getStorageSync('token')}`,
        'Content-Type': 'application/json'
      },
      data,
      success: res => resolve(res),
      fail: err => reject(err)
    });
  });
  if (res.data.code === 200) {
    const listKey = type === 'pet' ? 'pets' : 'blogs';
    const list = this.data[listKey].filter(item => item.id !== id);
    this.setData({ [listKey]: list });
  }
}

十一)项目是如何处理用户信息更新的?

解答:项目在 user/profile.js 中实现了用户信息更新功能。用户可以选择更新头像、昵称、真实姓名等信息。更新头像时,会先下载微信头像到本地临时文件,再上传到服务器。更新其他信息时,会发送 POST 请求将更新后的用户信息保存到服务器,并同步更新本地存储和全局状态。

代码片段

// 文件: user/profile.js
saveInfo() {
  const { userInfo, tempFilePath } = this.data;
  if (tempFilePath) {
    wx.uploadFile({
      url: `${app.globalData.baseUrl}/user/updatePic`,
      filePath: tempFilePath,
      name: 'file',
      header: {
        'token': wx.getStorageSync('token')
      },
      success: (res) => {
        const data = JSON.parse(res.data);
        if (data.code === 0) {
          userInfo.pic = data.data.url;
          this.updateUserInfo();
        }
      }
    });
  } else {
    this.updateUserInfo();
  }
}

十二)项目是如何处理用户注册的?

解答:项目在 user/register.js 中实现了用户注册功能。用户输入基本信息(用户名、密码、真实姓名等),并通过表单验证确保输入合法。注册时,会发送 POST 请求将用户信息发送到服务器,并根据响应结果提示用户注册成功或失败。

代码片段

// 文件: user/register.js
async onRegister() {
  if (!this.validateForm()) {
    return;
  }
  try {
    wx.showLoading({
      title: '注册中...',
      mask: true
    });
    const res = await new Promise((resolve, reject) => {
      wx.request({
        url: `${app.globalData.baseUrl}/user/register`,
        method: 'POST',
        data: {
          userName: this.data.username,
          password: this.data.password,
          role: 'USER',
          status: 1,
          ...(this.data.realName ? { realName: this.data.realName } : {}),
          ...(this.data.telephone ? { telephone: this.data.telephone } : {}),
          ...(this.data.email ? { email: this.data.email } : {}),
          ...(this.data.sex ? { sex: this.data.sex } : {}),
          ...(this.data.birthday ? { birthday: this.data.birthday } : {}),
          ...(this.data.description ? { description: this.data.description } : {})
        },
        success: res => resolve(res),
        fail: err => reject(err)
      });
    });
    if (res.statusCode === 200 && res.data.code === 200) {
      wx.showToast({
        title: '注册成功',
        icon: 'success'
      });
      setTimeout(() => {
        wx.navigateBack();
      }, 1500);
    } else {
      wx.showToast({
        title: res.data.message || '注册失败',
        icon: 'none'
      });
    }
  } catch (error) {
    wx.showToast({
      title: error.errMsg || '注册失败,请重试',
      icon: 'none'
    });
  } finally {
    wx.hideLoading();
  }
}

十三)项目是如何处理用户忘记密码的?

解答:项目在 user/forgot-password.js 中实现了用户忘记密码功能。用户输入用户名和手机号,并设置新密码。通过表单验证确保输入合法后,发送 POST 请求重置密码,并根据响应结果提示用户重置成功或失败。

代码片段

// 文件: user/forgot-password.js
async onResetPassword() {
  if (this.data.loading) {
    return;
  }
  if (!this.data.userName || !this.data.telephone) {
    this.setData({
      errorMsg: '请输入用户名和手机号'
    });
    return;
  }
  if (!/^1[3-9]\d{9}$/.test(this.data.telephone)) {
    this.setData({
      errorMsg: '请输入正确的手机号'
    });
    return;
  }
  if (!this.data.password || !this.data.confirmPassword) {
    this.setData({
      errorMsg: '请输入新密码'
    });
    return;
  }
  if (this.data.password !== this.data.confirmPassword) {
    this.setData({
      errorMsg: '两次输入的密码不一致'
    });
    return;
  }
  if (this.data.password.length < 6) {
    this.setData({
      errorMsg: '密码长度不能少于6位'
    });
    return;
  }
  this.setData({ loading: true });
  try {
    const res = await new Promise((resolve, reject) => {
      wx.request({
        url: `${app.globalData.baseUrl}/user/reset-password`,
        method: 'POST',
        header: {
          'Content-Type': 'application/json'
        },
        data: {
          userName: this.data.userName,
          telephone: this.data.telephone,
          password: this.data.password
        },
        success: res => resolve(res),
        fail: err => reject(err)
      });
    });
    if (res.statusCode === 200 && res.data.code === 200) {
      wx.showToast({
        title: '密码重置成功',
        icon: 'success'
      });
      setTimeout(() => {
        wx.navigateBack();
      }, 1500);
    } else {
      throw new Error(res.data.message || '重置密码失败');
    }
  } catch (error) {
    this.setData({
      errorMsg: typeof error === 'string' ? error : (error.message || error.errMsg || '重置密码失败,请重试')
    });
  } finally {
    this.setData({ loading: false });
  }
}

十四)项目是如何处理领养申请的?

解答:项目在 adopt/apply.js 中实现了领养申请功能。用户填写申请表单,包括姓名、电话、地址等信息,并选择房屋类型和是否租房等选项。提交表单时,会发送 POST 请求将表单数据发送到服务器,并根据响应结果提示用户申请成功或失败。

代码片段

// 文件: adopt/apply.js
submitForm(e) {
  const formData = e.detail.value;
  if (!this.validateForm(formData)) {
    return;
  }
  this.setData({ submitting: true });
  const requestData = {
    petId: this.data.petId,
    remark: JSON.stringify({
      name: formData.name,
      phone: formData.phone,
      wechat: formData.wechat,
      address: formData.address,
      houseType: this.data.houseTypes[formData.houseTypeIndex],
      isRent: formData.isRent,
      landlordAgree: formData.landlordAgree,
      familyMembers: formData.familyMembers,
      familyAgree: formData.familyAgree,
      hasPetExperience: formData.hasPetExperience,
      description: formData.description
    })
  };
  wx.request({
    url: `${app.globalData.baseUrl}/adopt/apply`,
    method: 'POST',
    header: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${token}`
    },
    data: requestData,
    success: (res) => {
      if (res.data.code === 200) {
        wx.showToast({
          title: '申请提交成功',
          icon: 'success'
        });
        setTimeout(() => {
          wx.navigateBack();
        }, 2000);
      } else {
        wx.showToast({
          title: res.data.message || '提交失败,请重试',
          icon: 'error'
        });
      }
    },
    fail: (error) => {
      wx.showToast({
        title: '网络错误,请重试',
        icon: 'error'
      });
    },
    complete: () => {
      this.setData({ submitting: false });
    }
  });
}

十五)项目是如何处理消息通知的?

解答:项目在 mp-weixin/app.js 中实现了消息通知功能。应用启动时会检查未读消息数量,并通过 wx.showTabBarRedDotwx.hideTabBarRedDot 方法显示或隐藏小红点。每隔 30 秒会自动检查一次未读消息数量。

代码片段

// 文件: mp-weixin/app.js
async checkUnreadMessages() {
  try {
    const token = this.globalData.token;
    const userInfo = this.globalData.userInfo;
    if (!token || !userInfo) {
      console.log('用户未登录,跳过检查未读消息');
      return;
    }
    const res = await new Promise((resolve, reject) => {
      wx.request({
        url: `${this.globalData.baseUrl}/messages/unread/count`,
        method: 'GET',
        header: {
          'Authorization': `Bearer ${token}`,
          'Content-Type': 'application/json'
        },
        data: {
          userId: userInfo.id
        },
        success: res => resolve(res),
        fail: err => reject(err)
      });
    });
    if (res.statusCode === 200 && res.data.code === 200) {
      const unreadCount = res.data.data || 0;
      this.globalData.unreadMessageCount = unreadCount;
      if (unreadCount > 0) {
        wx.showTabBarRedDot({
          index: 3
        });
      } else {
        wx.hideTabBarRedDot({
          index: 3
        });
      }
    }
  } catch (error) {
    console.error('检查未读消息失败:', error);
  }
}

十六)项目是如何处理领养申请详情的?

解答:项目在 adopt/detail.js 中实现了领养申请详情功能。页面加载时会发送 GET 请求获取申请详情,并解析 remark 字段为表单数据。此外,还提供了审核通过和拒绝的功能,确保管理员可以方便地处理领养申请。

代码片段

// 文件: adopt/detail.js
async loadAdoptDetail() {
  try {
    const token = wx.getStorageSync('token');
    const res = await new Promise((resolve, reject) => {
      wx.request({
        url: `${app.globalData.baseUrl}/adopt/detail/${this.data.id}`,
        method: 'GET',
        header: {
          'Authorization': 'Bearer ' + token,
          'Content-Type': 'application/json'
        },
        success: resolve,
        fail: reject
      });
    });
    if (res.data.code === 200) {
      const adoptInfo = res.data.data;
      let formData = {};
      try {
        if (adoptInfo.remark) {
          formData = JSON.parse(adoptInfo.remark);
        }
      } catch (error) {
        console.error('[申请详情] 解析表单数据失败:', error);
      }
      this.setData({
        adoptInfo,
        formData
      });
    }
  } catch (error) {
    wx.showToast({
      title: error.message || '加载失败',
      icon: 'none'
    });
  }
}

十七)项目是如何处理图片预览的?

解答:项目在 blog/publish.jsblog/edit.js 中实现了图片预览功能。通过 wx.previewImage 方法展示用户选择的图片。在 blog/publish.js 中,还会处理图片路径,确保预览图片的 URL 是完整的。

代码片段

// 文件: blog/publish.js
previewImage(e) {
  const src = e.currentTarget.dataset.src;
  const currentUrl = src.startsWith('http') ? src : `${app.globalData.baseUrl}${src}`;
  const urls = this.data.tempImages.map(img => {
    if (img.startsWith('http')) {
      return img;
    }
    return `${app.globalData.baseUrl}${img}`;
  });
  wx.previewImage({
    current: currentUrl,
    urls: urls
  });
}

十八)项目是如何处理用户信息更新的?

解答:项目在 user/profile.js 中实现了用户信息更新功能。用户可以修改昵称、真实姓名、手机号、邮箱等信息,并通过 wx.uploadFile 更新头像。更新信息时,会发送 POST 请求将更新后的用户信息发送到服务器,并同步更新本地存储和全局状态。

代码片段

// 文件: user/profile.js
async onSave() {
  try {
    wx.showLoading({
      title: '保存中...',
      mask: true
    });
    const updateData = {
      id: this.data.userInfo.id
    };
    const fields = ['userName', 'realName', 'telephone', 'email', 'sex', 'birthday', 'location', 'description', 'pic'];
    fields.forEach(field => {
      if (this.data.userInfo[field]) {
        updateData[field] = this.data.userInfo[field];
      }
    });
    const res = await new Promise((resolve, reject) => {
      wx.request({
        url: `${app.globalData.baseUrl}/user/update`,
        method: 'POST',
        data: updateData,
        header: {
          'Authorization': `Bearer ${wx.getStorageSync('token')}`,
          'Content-Type': 'application/json'
        },
        success: res => resolve(res),
        fail: err => reject(err)
      });
    });
    if (res.data.code === 200) {
      wx.setStorageSync('userInfo', this.data.userInfo);
      app.globalData.userInfo = this.data.userInfo;
      wx.showToast({
        title: '保存成功',
        icon: 'success'
      });
      setTimeout(() => {
        wx.navigateBack();
      }, 1500);
    }
  } catch (error) {
    wx.showToast({
      title: typeof error === 'string' ? error : (error.message || '保存失败'),
      icon: 'none'
    });
  } finally {
    wx.hideLoading();
  }
}

十九)项目是如何处理用户注销的?

解答:项目在 user/settings.js 中实现了用户注销功能。用户点击注销按钮后,会弹出确认框,确认后发送 POST 请求到后端进行注销操作。注销成功后,清除本地存储的用户信息,并跳转到登录页面。

代码片段

// 文件: user/settings.js
logout() {
  const that = this;
  wx.showModal({
    title: '提示',
    content: '确定要退出登录吗?',
    success: function(res) {
      if (res.confirm) {
        wx.request({
          url: `${getApp().globalData.baseUrl}/user/logout`,
          method: 'POST',
          header: {
            'Authorization': wx.getStorageSync('token')
          },
          success(res) {
            if (res.data.code === 0) {
              wx.removeStorageSync('userInfo');
              wx.removeStorageSync('token');
              that.setData({
                userInfo: null
              });
              wx.showToast({
                title: '已退出登录',
                icon: 'success'
              });
              setTimeout(() => {
                wx.reLaunch({
                  url: '/pages/user/index'
                });
              }, 1500);
            }
          }
        });
      }
    }
  });
}

二十)项目是如何处理宠物推荐的?

解答:项目在 index/index.js 中实现了宠物推荐功能。页面加载时会发送 GET 请求获取推荐宠物列表,并根据返回的数据动态生成宠物卡片。此外,还提供了跳转到推荐宠物列表的功能,确保用户可以方便地查看更多推荐宠物。

代码片段

// 文件: index/index.js
loadRecommendPets() {
  wx.request({
    url: `${getApp().globalData.baseUrl}/pets/recommend`,
    success: function(res) {
      if (res.data.code === 200) {
        const pets = res.data.extra.data || [];
        const formattedPets = pets.map(pet => {
          let images = [];
          let mainImage = '/static/images/pets/default.jpg';
          if (pet.pic) {
            images = pet.pic.split(',').map(pic => {
              pic = pic.trim();
              if (!pic.startsWith('http') && !pic.startsWith('https')) {
                if (pic.startsWith('/adopt')) {
                  pic = pic.substring(6);
                }
                if (!pic.startsWith('/')) {
                  pic = '/' + pic;
                }
                return `${getApp().globalData.baseUrl}${pic}`;
              }
              return pic;
            });
            mainImage = images[0];
          }
          return {
            ...pet,
            images,
            mainImage
          };
        });
        that.setData({ recommendPets: formattedPets });
      }
    }
  });
}

二十一)项目是如何处理领养记录的?

解答:项目在 user/adopts.js 中实现了领养记录功能。通过 wx.request 请求获取用户的领养记录,并根据返回的数据动态生成记录列表。每条记录包含了宠物信息、审核时间和状态等,并提供了查看详情的功能。

代码片段

// 文件: user/adopts.js
async loadRecords() {
  if (this.data.loading || !this.data.hasMore) {
    return;
  }
  try {
    this.setData({ loading: true });
    const token = wx.getStorageSync('token');
    const res = await new Promise((resolve, reject) => {
      wx.request({
        url: `${app.globalData.baseUrl}/adopt/approved`,
        method: 'GET',
        header: {
          'Authorization': `Bearer ${token}`,
          'Content-Type': 'application/json'
        },
        data: {
          page: this.data.page,
          size: this.data.size
        },
        success: res => resolve(res),
        fail: err => reject(err)
      });
    });
    if (res.data.code === 200) {
      const newRecords = res.data.data.list || [];
      newRecords.forEach(record => {
        if (!record.pet || !record.pet.petName) {
          record.pet = record.pet || {};
          record.pet.petName = '加载失败';
          record.pet.pic = '/static/images/icon/error-pet.png';
        }
        if (record.reviewTime) {
          const date = record.reviewTime.replace(' ', 'T');
          record.reviewTime = new Date(date).toLocaleDateString('zh-CN', {
            year: 'numeric',
            month: '2-digit',
            day: '2-digit'
          }).replace(/\//g, '-');
        } else {
          record.reviewTime = '未知时间';
        }
        if (record.pet && record.pet.pic) {
          record.pet.pic = record.pet.pic.split(',')[0];
          if (!record.pet.pic.startsWith('http')) {
            record.pet.pic = `${app.globalData.baseUrl}${record.pet.pic}`;
          }
        }
      });
      this.setData({
        records: [...this.data.records, ...newRecords],
        hasMore: newRecords.length === this.data.size
      });
    }
  } catch (error) {
    console.error('[adopts] 加载记录失败:', error);
    this.setData({ hasLoadError: true });
  } finally {
    this.setData({ loading: false });
  }
}

二十二)项目是如何处理博客发布的?

解答:项目在 blog/publish.js 中实现了博客发布功能。用户填写博客标题、内容、选择封面图片和标签后,通过 wx.uploadFile 方法上传图片,然后发送 POST 请求将博客信息发送到服务器。发布成功后,会提示用户并返回上一页。

代码片段

// 文件: blog/publish.js
submitForm(e) {
  if (!this.data.formData.title.trim()) {
    wx.showToast({
      title: '请输入故事标题',
      icon: 'none'
    });
    return;
  }
  if (!this.data.formData.content.trim()) {
    wx.showToast({
      title: '请输入故事内容',
      icon: 'none'
    });
    return;
  }
  if (this.data.tempImages.length === 0) {
    wx.showToast({
      title: '请至少上传一张图片',
      icon: 'none'
    });
    return;
  }
  wx.showLoading({
    title: '正在发布...',
    mask: true
  });
  this.uploadImages()
    .then(uploadedImages => {
      const submitData = {
        title: this.data.formData.title.trim(),
        content: this.data.formData.content.trim(),
        coverImage: uploadedImages.join(','),
        tags: this.data.selectedTags.join(','),
        status: 1
      };
      return new Promise((resolve, reject) => {
        wx.request({
          url: `${app.globalData.baseUrl}/blogs`,
          method: 'POST',
          data: submitData,
          header: {
            'Authorization': `Bearer ${cleanToken}`,
            'Content-Type': 'application/json'
          },
          success: resolve,
          fail: reject
        });
      });
    })
    .then(res => {
      if (res.statusCode === 200 && res.data.code === 200) {
        wx.showToast({
          title: '发布成功',
          icon: 'success'
        });
        setTimeout(() => {
          wx.navigateBack();
        }, 2000);
      }
    })
    .catch(err => {
      wx.showToast({
        title: typeof err === 'string' ? err : (err.message || '发布失败'),
        icon: 'none'
      });
    })
    .finally(() => {
      wx.hideLoading();
    });
}

二十三)项目是如何处理博客编辑的?

解答:项目在 blog/edit.js 中实现了博客编辑功能。页面加载时会发送 GET 请求获取博客详情,并填充到表单中。用户可以修改博客标题、内容、封面图片和标签,然后通过 wx.uploadFile 方法上传图片,最后发送 PUT 请求将修改后的博客信息保存到服务器。

代码片段

// 文件: blog/edit.js
async loadBlogDetail() {
  try {
    wx.showLoading({
      title: '加载中...',
      mask: true
    });
    const res = await new Promise((resolve, reject) => {
      wx.request({
        url: `${app.globalData.baseUrl}/blogs/${this.data.blogId}`,
        method: 'GET',
        success: resolve,
        fail: reject
      });
    });
    if (res.statusCode === 200 && res.data.code === 200) {
      const blog = res.data.data;
      let images = [];
      if (blog.coverImage) {
        images = blog.coverImage.split(',').map(pic => {
          pic = pic.trim();
          if (pic.startsWith('/static/')) {
            return pic;
          }
          if (!pic.startsWith('http') && !pic.startsWith('https')) {
            if (pic.startsWith('/adopt')) {
              pic = pic.substring(6);
            }
            if (!pic.startsWith('/')) {
              pic = '/' + pic;
            }
            return `${app.globalData.baseUrl}${pic}`;
          }
          return pic;
        });
      }
      let selectedTags = [];
      if (blog.tags) {
        selectedTags = blog.tags.split(',');
      }
      this.setData({
        'formData.title': blog.title,
        'formData.content': blog.content,
        contentLength: blog.content.length,
        tempImages: images,
        selectedTags
      });
      wx.hideLoading();
    }
  } catch (error) {
    console.error('加载博客详情失败:', error);
    wx.hideLoading();
    wx.showToast({
      title: error.message || '加载失败',
      icon: 'none'
    });
    setTimeout(() => wx.navigateBack(), 1500);
  }
}

二十四)项目是如何处理用户评论的?

解答:项目在 user/comments.js 中实现了用户评论功能。通过 wx.request 请求获取用户的评论列表,并根据返回的数据动态生成评论项。每条评论包含了评论内容、时间、宠物或博客信息等,并提供了删除评论的功能。

代码片段

// 文件: user/comments.js
loadComments(isRefresh = false) {
  if (this.data.loading) return;
  if (!isRefresh && !this.data.hasMore) return;
  const that = this;
  this.setData({ loading: true });
  wx.request({
    url: `${getApp().globalData.baseUrl}/user/comments`,
    method: 'GET',
    header: {
      'Authorization': wx.getStorageSync('token')
    },
    data: {
      page: isRefresh ? 1 : this.data.currentPage,
      size: this.data.pageSize
    },
    success(res) {
      if (res.data.code === 0) {
        const newList = res.data.data || [];
        that.setData({
          commentList: isRefresh ? newList : [...that.data.commentList, ...newList],
          currentPage: isRefresh ? 2 : that.data.currentPage + 1,
          hasMore: newList.length === that.data.pageSize
        });
      }
    },
    fail() {
      wx.showToast({
        title: '网络错误',
        icon: 'none'
      });
    },
    complete() {
      that.setData({ loading: false });
      wx.stopPullDownRefresh();
    }
  });
}

二十五)项目是如何处理用户收藏的?

解答:项目在 user/favorites.js 中实现了用户收藏功能。用户可以在收藏页面切换查看宠物收藏和博客收藏,并通过 wx.request 请求获取相应的收藏列表。每条收藏项包含了收藏对象的信息,并提供了取消收藏的功能。

代码片段

// 文件: user/favorites.js
async loadFavorites(refresh = false) {
  if (this.data.loading || (!refresh && !this.data.hasMore)) {
    return;
  }
  const token = wx.getStorageSync('token');
  this.setData({ loading: true });
  try {
    const url = this.data.activeTab === 'pets' 
      ? `${app.globalData.baseUrl}/favorites/list`
      : `${app.globalData.baseUrl}/blog/favorites/list`;
    const res = await new Promise((resolve, reject) => {
      wx.request({
        url,
        method: 'GET',
        header: {
          'Authorization': `Bearer ${token}`,
          'Content-Type': 'application/json'
        },
        data: {
          pageNum: refresh ? 1 : this.data.pageNum,
          pageSize: this.data.pageSize
        },
        success: res => resolve(res),
        fail: err => reject(err)
      });
    });
    if (res.data.code === 200) {
      const { data: pageInfo } = res.data;
      const list = this.data.activeTab === 'pets' ? pageInfo.list : pageInfo.list;
      const formattedList = list.map(item => {
        if (this.data.activeTab === 'pets' && item.pic) {
          let images = [];
          let mainImage = '/static/images/pets/default.jpg';
          images = item.pic.split(',').map(pic => {
            pic = pic.trim();
            if (!pic.startsWith('http') && !pic.startsWith('https')) {
              if (pic.startsWith('/adopt')) {
                pic = pic.substring(6);
              }
              if (!pic.startsWith('/')) {
                pic = '/' + pic;
              }
              return `${app.globalData.baseUrl}${pic}`;
            }
            return pic;
          });
          mainImage = images[0];
          return {
            ...item,
            images,
            mainImage
          };
        } else if (this.data.active

项目后端

一、项目的技术架构概况

  • 一句话概括:这是一个基于 SSM(Spring + SpringMVC + MyBatis)框架的宠物领养和管理平台,采用了 MVC 架构,利用 MyBatis 进行数据库操作,使用JWT进行身份验证,并通过自定义注解和拦截器实现权限控制。该项目具备完善的用户认证、权限控制、文件上传、微信登录等功能,适用于宠物领养和管理场景。

一)技术架构主要包括

  • Spring:负责管理应用中的对象(Bean),协调各个组件之间的依赖关系,提供事务管理等服务。
  • Spring MVC:作为 Web 框架,处理 HTTP 请求和响应,提供 RESTful API 接口。
  • MyBatis:作为 ORM(对象关系映射)工具,简化数据库操作,与 Spring 结合使用,提供强大的 SQL 映射功能。
  • MySQL:作为数据库管理系统,负责存储宠物信息、用户信息、领养申请信息、审核记录等各类数据。
  • JWT (JSON Web Token):用于用户认证和授权,通过在HTTP头部传递token实现无状态的认证机制。
  • PageHelper:用于分页查询,简化了分页逻辑的实现。
  • Lombok:减少样板代码,如getter、setter等,提高开发效率。
  • Servlet:用于处理上传目录初始化等Web容器相关的操作。

二)运行环境包括

  • Java 8 或更高版本:确保兼容性和性能。
  • Maven:作为构建工具,管理依赖和构建项目。
  • MySQL:作为关系型数据库管理系统,存储应用程序数据。
  • Tomcat :作为Web服务器,运行应用程序。

三)项目结构分析

1. 项目目录结构
  • backend/src/main/java/pet:包含项目的Java代码,具体分为以下几个子模块:
    • annotation:自定义注解,如RequireRole.java,用于权限控制。
    • config:配置类,如UploadDirectoryInitializer.javaWebConfig.javaWechatConfig.java,用于初始化上传目录、配置跨域访问和微信登录等。
    • controller:控制器层,处理HTTP请求,如AdoptController.javaUserController.java等。
    • exception:自定义异常类,如AuthorizationException.javaBusinessException.java,用于处理不同类型的异常。
    • interceptor:拦截器类,如LoginInterceptor.javaRoleInterceptor.java,用于拦截请求进行权限验证。
    • mapper:MyBatis映射接口,如AdoptMapper.javaUserMapper.java,用于与数据库交互。
    • model:实体类,如AdoptRecord.javaUser.java,表示数据库中的表结构。
    • service:服务层接口和实现类,如AdoptService.javaAdoptServiceImpl.java,封装业务逻辑。
    • utils:工具类,如FileLoad.javaJwtUtil.java,提供文件上传、JWT生成和解析等功能。
    • Application.java:应用程序的入口类,配置了组件扫描和Web MVC。
2. 关键文件和功能
  • UploadDirectoryInitializer.java:实现了ServletContextListener接口,在应用启动时初始化上传目录。
  • WebConfig.java:配置了跨域资源共享(CORS),允许前端跨域访问后端API。
  • WechatConfig.java:配置了微信登录的相关参数,如App ID、App Secret等。
  • LoginInterceptor.java:拦截请求,检查用户是否登录,确保未登录用户不能访问受保护的资源。
  • RoleInterceptor.java:进一步检查用户角色,确保只有具有适当权限的用户可以访问特定资源。
  • AdoptController.java:处理领养申请的增删改查操作,涉及领养申请的提交、审批、拒绝和取消等。
  • UserController.java:处理用户相关的操作,如注册、登录、微信登录、修改个人信息等。
  • FileLoad.java:提供了文件上传和删除的功能,支持宠物图片、用户头像等文件的上传。
  • JwtUtil.java:提供了JWT的生成、解析和验证功能,确保用户身份的安全性。
  • Result.java:用于统一API响应格式,封装了成功和失败的响应结果。
3. 数据库相关
  • resources/mapper/*.xml:MyBatis的XML映射文件,定义了SQL语句和实体类之间的映射关系。
  • database.properties:配置了数据库连接信息,如URL、用户名和密码。
  • upload.properties:配置了文件上传的基础目录和其他相关参数。
  • wechat.properties:配置了微信登录的相关参数。
4. 配置文件
  • applicationContext.xml:Spring的配置文件,配置了Bean和其他Spring相关设置。
  • mybatis-config.xml:MyBatis的全局配置文件,配置了MyBatis的行为和属性。
  • springmvc-servlet.xml:Spring MVC的配置文件,配置了视图解析器、拦截器等。
  • web.xml:传统的Web应用配置文件。
5. 前端静态资源
  • webapp/static:存放前端静态资源,如HTML、CSS、JavaScript文件。
  • webapp/static/admin/pages:管理员页面的静态资源。
  • webapp/static/images/banner:存放轮播图的图片资源。
6. 微信小程序部分
  • mp-weixin:存放微信小程序的代码,包括页面、组件和配置文件。
  • components:小程序组件,如评论组件。
  • pages:小程序页面,如领养、博客、评论、消息等。
  • utils/imagePathUtils.js:提供了图像路径处理的工具函数。

二、主要业务功能介绍

以下业务功能涵盖了一个宠物领养平台的主要需求,包括用户管理、宠物管理、领养管理、评论与互动、消息管理、统计信息等。

  1. 用户管理

    • 用户可以通过登录页面输入账号和密码进行系统登录,支持普通用户和管理员两种身份。
    • 支持微信登录,用户可以通过微信授权登录系统。
    • 用户可以注册账号,填写用户名、密码、真实姓名、电话、邮箱等信息。
    • 用户可以更新个人信息,包括头像、密码、昵称等。
    • 管理员可以查看、编辑、删除用户信息,并管理用户状态(启用/禁用)。
    • 用户可以查看自己的收藏、评论、领养申请等信息。
    • 用户可以重置密码,需要提供用户名和绑定的手机号进行验证。
  2. 宠物管理

    • 用户可以发布宠物信息,填写宠物名称、类型、品种、性别、体重、生日、疫苗接种情况、绝育情况、健康状况等。
    • 用户可以上传宠物图片,支持多张图片上传。
    • 用户可以更新或删除自己发布的宠物信息。
    • 用户可以查看推荐宠物、宠物列表以及特定宠物的详细信息。
    • 用户可以收藏感兴趣的宠物,并查看自己的收藏列表。
    • 用户可以增加宠物的浏览次数。
    • 管理员可以批量上传宠物图片,支持多张图片同时上传。
  3. 领养管理

    • 用户可以提交领养申请,选择想要领养的宠物并填写相关信息。
    • 用户可以查看自己的领养申请记录,包括申请状态(申请中、已通过、已拒绝、已取消)。
    • 管理员或宠物发布者可以审核领养申请,审核通过后更新宠物状态为“已领养”。
    • 管理员或宠物发布者可以拒绝领养申请,并填写拒绝理由。
    • 用户可以取消自己的领养申请,但只能取消处于“申请中”的申请。
    • 用户可以查看已通过的领养记录,并加载完整的宠物信息。
  4. 评论与互动

    • 用户可以在宠物或博客页面发表评论,并查看评论列表。
    • 用户可以对评论进行点赞或取消点赞,查看自己点赞过的评论。
    • 用户可以对评论进行回复,并查看回复列表。
    • 用户可以删除自己的评论或回复。
    • 用户可以查看所有评论(仅管理员有权限)。
  5. 博客管理

    • 用户可以发布博客,填写标题、内容、封面图片、分类、标签等信息。
    • 用户可以更新或删除自己发布的博客。
    • 用户可以收藏感兴趣的博客,并查看自己的收藏列表。
    • 用户可以查看最新的博客列表。
    • 用户可以增加博客的浏览次数。
    • 用户可以对博客进行点赞或取消点赞。
  6. 消息管理

    • 用户之间可以通过私信进行交流,支持初始化会话、发送消息、查看会话列表和消息列表。
    • 用户可以标记消息为已读,查看未读消息数量。
    • 用户可以删除会话中的消息。
  7. 统计信息

    • 管理员可以查看用户总数、宠物总数、领养申请总数和评论总数。
    • 管理员可以查看特定用户的收藏数量、领养申请数量等。
  8. 权限控制

    • 系统通过拦截器实现权限控制,确保不同角色的用户只能访问其有权限的资源。
    • 普通用户只能访问公开资源和个人相关资源,管理员可以访问所有资源并进行管理操作。
    • 某些特定操作需要特定角色(如管理员或宠物发布者)才能执行。
  9. 轮播图管理

    • 管理员可以添加、编辑、删除轮播图,设置轮播图的顺序和状态(激活/未激活)。
    • 用户可以查看活跃的轮播图列表。
  10. 全局异常处理

    • 系统提供了全局异常处理器,捕获并处理所有未捕获的异常,返回友好的错误信息给用户。
  11. 文件上传与下载

    • 系统支持多种类型的文件上传,包括宠物图片、用户头像、博客封面图片等。
    • 系统支持文件删除功能,确保用户上传的文件在不再需要时可以被删除。
  12. JWT 认证

    • 系统使用 JWT(JSON Web Token)进行用户认证,确保每次请求都经过有效的身份验证。
    • 用户登录后会生成 JWT Token,后续请求需要携带此 Token 进行身份验证。

以上功能模块共同构成了宠物领养平台的核心业务逻辑,确保用户能够方便地发布、管理和领养宠物,同时也为管理员提供了强大的管理工具。

三、各文件的作用

  • RequireRole.java

    • 自定义注解,用于方法级别的权限控制,指定哪些角色可以访问该方法。
  • UploadDirectoryInitializer.java

    • 实现ServletContextListener接口,在应用启动时初始化上传目录,确保上传目录及其子目录存在。
  • WebConfig.java

    • 配置Spring MVC的跨域资源共享(CORS),允许来自不同域的请求访问后端接口。
  • WechatConfig.java

    • 配置微信相关参数,如appId、appSecret等,用于微信登录和其他微信接口的调用。
  • TestBannerController.java

    • 测试轮播图控制器,用于测试轮播图相关功能(具体实现未提供)。
  • AdoptController.java

    • 处理与领养申请相关的请求,包括提交申请、获取申请列表、查看详情、取消申请、审核申请等。
  • AnswerController.java

    • 处理与回复相关的请求,包括发表回复、删除回复、获取回复详情、点赞和取消点赞等。
  • BannerController.java

    • 处理与轮播图相关的请求,包括获取活动轮播图、保存、更新和删除轮播图等。
  • BlogController.java

    • 处理与博客相关的请求,包括获取最新博客、创建博客、更新博客、删除博客、上传博客图片、增加浏览次数等。
  • BlogFavoriteController.java

    • 处理与博客收藏相关的请求,包括添加收藏、取消收藏、检查是否已收藏、获取收藏列表等。
  • CommentController.java

    • 处理与评论相关的请求,包括获取评论列表、发表评论、删除评论、点赞和取消点赞等。
  • FavoriteController.java

    • 处理与宠物收藏相关的请求,包括添加收藏、取消收藏、检查是否已收藏、获取收藏列表等。
  • MessageController.java

    • 处理与私信消息相关的请求,包括初始化会话、发送消息、获取会话列表、分页获取会话消息列表、标记消息为已读、获取未读消息数等。
  • PetController.java

    • 处理与宠物相关的请求,包括获取宠物列表、推荐宠物、获取宠物详情、创建宠物、上传宠物图片、更新宠物信息、删除宠物信息、获取救助者发布的宠物列表、获取宠物类型和品种等。
  • StatsController.java

    • 处理统计数据相关的请求,包括获取用户总数、宠物总数、领养申请总数、评论总数等,主要用于管理员查看统计数据。
  • UserController.java

    • 处理与用户相关的请求,包括用户注册、登录、微信登录、登出、获取用户列表、更新用户信息、删除用户、获取用户信息、重置密码等。
  • AuthorizationException.java

    • 定义权限异常类,用于抛出权限不足的异常。
  • BusinessException.java

    • 定义业务异常类,用于抛出业务逻辑错误的异常。
  • GlobalExceptionHandler.java

    • 全局异常处理器,捕获并处理所有未捕获的异常,返回统一的错误响应。
  • LoginInterceptor.java

    • 拦截器,用于拦截请求并处理登录逻辑,确保用户已经登录,允许特定路径的匿名访问。
  • RoleInterceptor.java

    • 拦截器,用于拦截请求并检查用户角色权限,确保用户有权限访问特定资源。
  • AdoptMapper.java

    • MyBatis Mapper接口,用于领养记录的数据访问操作,如查询、插入、更新、删除等。
  • AnswerMapper.java

    • MyBatis Mapper接口,用于回复的数据访问操作,如查询、插入、更新、删除等。
  • BannerMapper.java

    • MyBatis Mapper接口,用于轮播图的数据访问操作,如查询、插入、更新、删除等。
  • BlogFavoriteMapper.java

    • MyBatis Mapper接口,用于博客收藏的数据访问操作,如查询、插入、更新、删除等。
  • BlogMapper.java

    • MyBatis Mapper接口,用于博客的数据访问操作,如查询、插入、更新、删除等。
  • CommentLikeMapper.java

    • MyBatis Mapper接口,用于评论点赞的数据访问操作,如查询、插入、更新、删除等。
  • CommentMapper.java

    • MyBatis Mapper接口,用于评论的数据访问操作,如查询、插入、更新、删除等。
  • FavoriteMapper.java

    • MyBatis Mapper接口,用于宠物收藏的数据访问操作,如查询、插入、更新、删除等。
  • MessageMapper.java

    • MyBatis Mapper接口,用于私信消息的数据访问操作,如查询、插入、更新、删除等。
  • MessageSessionMapper.java

    • MyBatis Mapper接口,用于私信会话的数据访问操作,如查询、插入、更新、删除等。
  • PetMapper.java

    • MyBatis Mapper接口,用于宠物的数据访问操作,如查询、插入、更新、删除等。
  • UserMapper.java

    • MyBatis Mapper接口,用于用户的数据访问操作,如查询、插入、更新、删除等。
  • AdoptRecord.java

    • 定义领养记录实体类,包含领养申请的相关字段和状态常量。
  • Answer.java

    • 定义回复实体类,包含回复的相关字段,默认值设置及关联的用户和评论信息。
  • Banner.java

    • 定义轮播图实体类,包含轮播图的相关字段和状态常量。
  • Blog.java

    • 定义博客实体类,包含博客的相关字段和状态常量,默认值设置及关联的作者和评论信息。
  • BlogFavorite.java

    • 定义博客收藏实体类,包含博客收藏的相关字段。
  • BlogFavoriteRequest.java

    • 定义博客收藏请求的DTO类,用于接收前端传递的博客收藏请求参数。
  • Comment.java

    • 定义评论实体类,包含评论的相关字段,默认值设置及关联的用户、宠物、博客和回复信息。
  • CommentLike.java

    • 定义评论点赞实体类,包含评论点赞的相关字段。
  • Favorite.java

    • 定义宠物收藏实体类,包含宠物收藏的相关字段,默认值设置及关联的用户和宠物信息。
  • Message.java

    • 定义私信消息实体类,包含私信消息的相关字段及关联的发送者信息。
  • MessageSession.java

    • 定义私信会话实体类,包含私信会话的相关字段及关联的两个用户信息。
  • Pet.java

    • 定义宠物实体类,包含宠物的相关字段和状态常量,默认值设置及关联的救助者和评论信息。
  • User.java

    • 定义用户实体类,包含用户的相关字段和角色、状态常量,提供判断用户角色和状态的方法。
  • AdoptServiceImpl.java

    • 领养服务的实现类,处理领养申请的业务逻辑,如提交申请、获取申请列表、查看详情、更新申请状态等。
  • AnswerServiceImpl.java

    • 回复服务的实现类,处理回复的业务逻辑,如发表回复、删除回复、获取回复列表、点赞和取消点赞等。
  • BannerServiceImpl.java

    • 轮播图服务的实现类,处理轮播图的业务逻辑,如获取活动轮播图、保存、更新和删除轮播图等。
  • BlogFavoriteServiceImpl.java

    • 博客收藏服务的实现类,处理博客收藏的业务逻辑,如添加收藏、取消收藏、检查是否已收藏、获取收藏列表等。
  • BlogServiceImpl.java

    • 博客服务的实现类,处理博客的业务逻辑,如获取博客详情、创建博客、更新博客、删除博客等。
  • CommentServiceImpl.java

    • 评论服务的实现类,处理评论的业务逻辑,如发表评论、删除评论、获取评论列表、点赞和取消点赞等。
  • FavoriteServiceImpl.java

    • 宠物收藏服务的实现类,处理宠物收藏的业务逻辑,如添加收藏、取消收藏、检查是否已收藏、获取收藏列表等。
  • MessageServiceImpl.java

    • 私信消息服务的实现类,处理私信消息的业务逻辑,如初始化会话、发送消息、获取会话列表、分页获取会话消息列表、标记消息为已读、获取未读消息数等。
  • PetServiceImpl.java

    • 宠物服务的实现类,处理宠物的业务逻辑,如获取宠物详情、创建宠物、更新宠物信息、删除宠物信息、获取宠物列表、推荐宠物等。
  • UserServiceImpl.java

    • 用户服务的实现类,处理用户的业务逻辑,如用户登录、注册、微信登录、更新用户信息、删除用户、获取用户列表、重置密码等。
  • AdoptService.java

    • 定义领养服务接口,声明领养申请相关的业务方法。
  • AnswerService.java

    • 定义回复服务接口,声明回复相关的业务方法。
  • BannerService.java

    • 定义轮播图服务接口,声明轮播图相关的业务方法。
  • BlogFavoriteService.java

    • 定义博客收藏服务接口,声明博客收藏相关的业务方法。
  • BlogService.java

    • 定义博客服务接口,声明博客相关的业务方法。
  • CommentService.java

    • 定义评论服务接口,声明评论相关的业务方法。
  • FavoriteService.java

    • 定义宠物收藏服务接口,声明宠物收藏相关的业务方法。
  • MessageService.java

    • 定义私信消息服务接口,声明私信消息相关的业务方法。
  • PetService.java

    • 定义宠物服务接口,声明宠物相关的业务方法。
  • UserService.java

    • 定义用户服务接口,声明用户相关的业务方法。
  • FileLoad.java

    • 文件上传工具类,提供上传和删除文件的功能,支持多种类型的文件上传(如宠物图片、用户头像、博客图片等)。
  • JwtUtil.java

    • JWT工具类,用于生成、解析和验证JWT Token,包含从Token中获取用户ID和角色的方法。
  • Result.java

    • API响应结果类,定义统一的API响应格式,包含状态码、消息和数据,支持添加额外数据。
  • Application.java

    • 应用程序的入口配置类,配置Spring MVC的组件扫描和Web配置,实现WebMvcConfigurer接口。

四、主要业务功能在代码中的具体实现 & 调用过程

以下是一些主要业务功能的代码调用过程,涉及控制器(Controller)层接收请求、服务(Service)层处理业务逻辑、数据访问对象(DAO)层与数据库交互,以及实体(Entity)层表示数据模型。这些调用过程涵盖了用户认证、数据管理、业务逻辑处理等关键方面,展示了系统如何响应用户请求并处理数据。

一)用户登录流程

  • 用户访问登录页面并通过 POST 请求提交登录表单(用户名、密码)到 /user/login
  • UserControllerloginuser 方法通过 @PostMapping("/login") 接收这些数据。
  • 方法内部首先验证用户名是否为空,若为空则返回失败提示。
  • 调用 userService.loginuser 方法验证用户名和密码,若验证成功则生成 JWT Token 并将其返回给客户端。
  • 将用户信息存入 session,并返回包含用户信息和 token 的 JSON 响应。

代码片段

@PostMapping("/login")
public Result loginuser(HttpSession session, @RequestBody User loginRequest) {
    System.out.println("Login attempt - userName: " + loginRequest.getUserName());
    if (loginRequest.getUserName() == null || loginRequest.getUserName().trim().isEmpty()) {
        System.out.println("Login failed - username is empty");
        return Result.fail("用户名不能为空");
    }
    try {
        User user = userService.loginuser(loginRequest.getUserName(), loginRequest.getPassword());
        if (user != null) {
            if (user.getPic() == null || user.getPic().isEmpty()) {
                user.setPic(FileLoad.getDefaultAvatar());
                userService.update(user);
            }
            String token = JwtUtil.generateToken(user);
            session.setAttribute("user", user);
            System.out.println("Login successful for user: " + user.getUserName() + ", role: " + user.getRole());
            Map<String, Object> data = new HashMap<>();
            data.put("user", user);
            data.put("token", token);
            data.put("isAdmin", user.isAdmin());
            data.put("role", user.getRole());
            return Result.success(data);
        } else {
            System.out.println("Login failed - invalid credentials for user: " + loginRequest.getUserName());
            return Result.fail("用户名或密码错误");
        }
    } catch (Exception e) {
        System.err.println("Login error for user: " + loginRequest.getUserName());
        e.printStackTrace();
        return Result.fail("登录失败,请重试");
    }
}

二)领养申请流程

  • 用户访问领养申请页面并通过 POST 请求提交申请表单(领养记录)到 /adopt/apply
  • AdoptControllerapply 方法通过 @PostMapping("/apply") 接收这些数据。
  • 方法内部首先从请求头中提取 token,并验证其有效性。
  • 如果 token 有效,解析出用户 ID 并设置到领养记录中。
  • 调用 adoptService.apply 方法将领养记录保存到数据库,并更新宠物状态为“申请中”。
  • 返回操作结果,成功则返回申请提交成功的提示,失败则返回相应错误信息。

代码片段

@PostMapping("/apply")
public Result apply(@RequestBody AdoptRecord adoptRecord, HttpServletRequest request) {
    try {
        String token = request.getHeader("Authorization");
        if (token == null || !token.startsWith("Bearer ")) {
            return Result.fail("请先登录");
        }
        token = token.substring(7);
        Integer userId = JwtUtil.getUserId(token);
        if (userId == null) {
            return Result.fail("登录已过期,请重新登录");
        }
        adoptRecord.setUserId(userId);
        adoptRecord.setState(AdoptRecord.STATUS_PENDING);
        adoptRecord.setCreateTime(new Date());
        adoptRecord.setUpdateTime(new Date());
        boolean success = adoptService.apply(adoptRecord);
        if (success) {
            return Result.success("申请提交成功");
        } else {
            return Result.fail("申请提交失败");
        }
    } catch (Exception e) {
        return Result.fail("申请提交失败:" + e.getMessage());
    }
}

三)宠物管理

  • 用户访问宠物管理页面并通过 POST 请求提交创建宠物表单(宠物信息)到 /pets
  • PetControllercreatePet 方法通过 @PostMapping 接收这些数据。
  • 方法内部首先从请求头中提取 token,并验证其有效性。
  • 如果 token 有效,解析出用户 ID 并设置为宠物的救助者 ID。
  • 调用 petService.createPet 方法将宠物信息保存到数据库。
  • 返回操作结果,成功则返回创建成功的宠物信息,失败则返回相应错误信息。

代码片段

@PostMapping
@RequireRole({User.ROLE_USER, User.ROLE_ADMIN})
public Result<Pet> createPet(@RequestBody Pet pet, @RequestHeader("Authorization") String token) {
    try {
        String cleanToken = token.replace("Bearer ", "").trim();
        User user = userService.getUserByToken(cleanToken);
        if (user == null) {
            return Result.fail("请先登录");
        }
        pet.setRescuerId(user.getId());
        pet.setCreateTime(new Date());
        pet.setUpdateTime(new Date());
        pet.setState(Pet.STATUS_AVAILABLE);
        Pet savedPet = petService.createPet(pet);
        return Result.success(savedPet);
    } catch (Exception e) {
        return Result.fail("创建宠物信息失败:" + e.getMessage());
    }
}

四)博客管理

  • 用户访问博客管理页面并通过 POST 请求提交创建博客表单(博客信息)到 /blogs
  • BlogControllercreateBlog 方法通过 @PostMapping 接收这些数据。
  • 方法内部首先从请求头中提取 token,并验证其有效性。
  • 如果 token 有效,解析出用户 ID 并设置为博客的作者 ID。
  • 调用 blogService.createBlog 方法将博客信息保存到数据库。
  • 返回操作结果,成功则返回创建成功的博客信息,失败则返回相应错误信息。

代码片段

@PostMapping
public Result<Blog> createBlog(@RequestBody Blog blog, @RequestHeader("Authorization") String token) {
    try {
        if (token == null || !token.startsWith("Bearer ")) {
            return Result.fail("请先登录");
        }
        String actualToken = token.substring(7);
        User user = JwtUtil.getUser(actualToken);
        if (user == null) {
            return Result.fail("请先登录");
        }
        blog.setAuthorId(user.getId());
        Blog createdBlog = blogService.createBlog(blog);
        return Result.success(createdBlog);
    } catch (Exception e) {
        e.printStackTrace();
        return Result.error("创建博客失败:" + e.getMessage());
    }
}

五)评论管理

  • 用户访问评论管理页面并通过 POST 请求提交评论表单(评论信息)到 /pets/{petId}/comments/blogs/{blogId}/comments
  • CommentControlleraddPetCommentaddBlogComment 方法分别通过 @PostMapping("/pets/{petId}/comments")@PostMapping("/blogs/{blogId}/comments") 接收这些数据。
  • 方法内部首先从请求头中提取 token,并验证其有效性。
  • 如果 token 有效,解析出用户 ID 并设置到评论中。
  • 调用 commentService.addComment 方法将评论信息保存到数据库。
  • 返回操作结果,成功则返回创建成功的评论信息,失败则返回相应错误信息。

代码片段

@PostMapping("/pets/{petId}/comments")
public Result<Comment> addPetComment(
        @PathVariable Integer petId,
        @RequestBody Comment comment,
        @RequestHeader("Authorization") String token) {
    if (token == null || !token.startsWith("Bearer ")) {
        return Result.fail("请先登录");
    }
    String actualToken = token.substring(7);
    User user = JwtUtil.getUser(actualToken);
    if (user == null) {
        return Result.fail("请先登录");
    }
    comment.setPetId(petId);
    comment.setUserId(user.getId());
    Comment saved = commentService.addComment(comment);
    return Result.success(saved);
}

@PostMapping("/blogs/{blogId}/comments")
public Result<Comment> addBlogComment(
        @PathVariable Integer blogId,
        @RequestBody Comment comment,
        @RequestHeader("Authorization") String token) {
    if (token == null || !token.startsWith("Bearer ")) {
        return Result.fail("请先登录");
    }
    String actualToken = token.substring(7);
    User user = JwtUtil.getUser(actualToken);
    if (user == null) {
        return Result.fail("请先登录");
    }
    comment.setBlogId(blogId);
    comment.setUserId(user.getId());
    Comment saved = commentService.addComment(comment);
    return Result.success(saved);
}

六)私信管理

  • 用户访问私信管理页面并通过 POST 请求提交初始化会话表单(用户 ID1、用户 ID2、宠物 ID)到 /messages/session/init
  • MessageControllerinitSession 方法通过 @PostMapping("/session/init") 接收这些数据。
  • 方法内部首先从请求头中提取 token,并验证其有效性。
  • 如果 token 有效,解析出当前用户 ID,并验证其是否为发起会话的两个用户之一。
  • 调用 messageService.initSession 方法初始化会话,并返回会话信息。
  • 返回操作结果,成功则返回初始化会话成功的提示及会话信息,失败则返回相应错误信息。

代码片段

@PostMapping("/session/init")
public Result initSession(@RequestBody Map<String, Object> params, HttpServletRequest request) {
    try {
        System.out.println("开始初始化会话,参数:" + params);
        String token = request.getHeader("Authorization");
        if (token == null || !token.startsWith("Bearer ")) {
            return Result.fail("请先登录");
        }
        token = token.substring(7);
        Integer currentUserId = JwtUtil.getUserId(token);
        if (currentUserId == null) {
            return Result.fail("登录已过期,请重新登录");
        }
        Integer userId1 = (Integer) params.get("userId1");
        Integer userId2 = (Integer) params.get("userId2");
        if (userId1 == null || userId2 == null) {
            return Result.fail("用户ID不能为空");
        }
        if (!currentUserId.equals(userId1) && !currentUserId.equals(userId2)) {
            return Result.fail("无权限初始化此会话");
        }
        if (userId1.equals(userId2)) {
            return Result.fail("不能给自己发私信");
        }
        String type = (String) params.get("type");
        Integer sourceId = (Integer) params.get("sourceId");
        Integer petId = null;
        if ("pet".equals(type)) {
            petId = sourceId;
        }
        MessageSession session = messageService.initSession(userId1, userId2, petId);
        System.out.println("会话初始化成功:" + session);
        return Result.success("初始化会话成功").add("data", session);
    } catch (Exception e) {
        System.out.println("初始化会话失败:" + e.getMessage());
        e.printStackTrace();
        return Result.fail("初始化会话失败: " + e.getMessage());
    }
}

七)文件上传与下载

  • 用户访问文件上传页面并通过 POST 请求上传文件(如宠物图片、用户头像等)到 /pets/upload/uploadAvatar 等接口。
  • PetControlleruploadPetImages 方法或 UserControllerupdatePic 方法通过 @PostMapping 接收文件。
  • 方法内部首先从请求头中提取 token,并验证其有效性。
  • 如果 token 有效,解析出用户 ID 或宠物 ID,并调用 FileLoad.uploadPetPicFileLoad.uploadUserPic 方法上传文件。
  • 返回操作结果,成功则返回上传成功的文件路径,失败则返回相应错误信息。

代码片段

@PostMapping("/{id}/images")
@RequireRole({User.ROLE_USER, User.ROLE_ADMIN})
public Result<String> uploadPetImages(@PathVariable Integer id, 
        @RequestParam("files") MultipartFile[] files,
        HttpSession session) {
    try {
        Pet pet = petService.getPetById(id);
        if (pet == null) {
            return Result.fail("宠物不存在");
        }
        User user = (User) session.getAttribute("user");
        if (!User.ROLE_ADMIN.equals(user.getRole()) && !pet.getRescuerId().equals(user.getId())) {
            return Result.fail("只有宠物的救助者才能上传图片");
        }
        StringBuilder imageUrls = new StringBuilder();
        for (MultipartFile file : files) {
            String fileName = FileLoad.uploadPetPic(file);
            if (imageUrls.length() > 0) {
                imageUrls.append(",");
            }
            imageUrls.append(fileName);
        }
        pet.setPic(imageUrls.toString());
        pet.setUpdateTime(new Date());
        petService.updatePet(pet);
        return Result.success(imageUrls.toString());
    } catch (Exception e) {
        return Result.fail("上传图片失败:" + e.getMessage());
    }
}

@PostMapping("/updatePic")
public Result updatePic(@RequestHeader("Authorization") String token, @RequestParam MultipartFile file) {
    try {
        if (token == null || !token.startsWith("Bearer ")) {
            return Result.error("请先登录");
        }
        String cleanToken = token.replace("Bearer ", "").trim();
        Integer userId = JwtUtil.getUserId(cleanToken);
        if (userId == null) {
            return Result.error("登录已过期,请重新登录");
        }
        User user = userService.findById(userId);
        if (user == null) {
            return Result.error("用户不存在");
        }
        if (user.getPic() != null && !user.getPic().isEmpty() && !user.getPic().equals(FileLoad.getDefaultAvatar())) {
            FileLoad.deleteFile(user.getPic());
        }
        String fileName = FileLoad.uploadUserPic(file);
        if (fileName == null) {
            return Result.error("上传失败");
        }
        user.setPic(fileName);
        Integer update = userService.update(user);
        if (update > 0) {
            return Result.success().add("data", new HashMap<String, String>() {{
                put("pic", fileName);
            }});
        } else {
            FileLoad.deleteFile(fileName);
            return Result.error("更新失败");
        }
    } catch (IOException e) {
        return Result.error(e.getMessage());
    } catch (Exception e) {
        return Result.error("服务器错误");
    }
}

八)微信登录流程

  • 用户访问微信登录页面并通过 POST 请求提交微信登录表单(code 和 userInfo)到 /user/wx-login
  • UserControllerwxLogin 方法通过 @PostMapping("/wx-login") 接收这些数据。
  • 方法内部首先验证参数是否完整,若不完整则返回失败提示。
  • 调用 userService.wxLogin 方法通过微信 API 获取用户信息并创建或更新用户记录。
  • 生成 JWT Token 并将其返回给客户端。
  • 返回操作结果,成功则返回登录成功的提示及用户信息,失败则返回相应错误信息。

代码片段

@PostMapping("/wx-login")
public Result wxLogin(@RequestBody Map<String, Object> loginRequest, HttpSession session) {
    try {
        System.out.println("收到微信登录请求: " + loginRequest);
        String code = (String) loginRequest.get("code");
        Map<String, Object> userInfo = (Map<String, Object>) loginRequest.get("userInfo");
        if (code == null || userInfo == null) {
            System.err.println("登录失败,缺少必要参数 - code: " + code + ", userInfo: " + userInfo);
            return Result.fail("登录失败,缺少必要参数");
        }
        User user = userService.wxLogin(code, userInfo);
        if (user != null) {
            String token = JwtUtil.generateToken(user);
            session.setAttribute("user", user);
            System.out.println("微信登录成功 - 用户: " + user.getUserName() + ", token: " + token);
            Map<String, Object> data = new HashMap<>();
            data.put("user", user);
            data.put("token", token);
            return Result.success(data);
        } else {
            System.err.println("微信登录失败 - 用户信息为空");
            return Result.fail("微信登录失败");
        }
    } catch (Exception e) {
        System.err.println("微信登录异常: " + e.getMessage());
        e.printStackTrace();
        return Result.fail("微信登录失败," + e.getMessage());
    }
}

五、侧重点功能详细讲解

一)用户登录与权限管理

  • 代码实现逻辑
    • UserController中的loginuser方法接收用户提交的登录信息。
    • 通过UserServiceloginuser方法验证用户名和密码。
    • 使用JwtUtil生成JWT Token,并将用户信息存入Session。
    • 如果用户存在但已被禁用,则返回登录失败。
    • 如果用户不存在或密码错误,则返回登录失败。
    • 设置默认头像和默认角色。
  • 调用过程
    • 用户在登录页面填写用户名和密码并提交。
    • UserControllerloginuser方法被触发,执行用户验证逻辑。
    • 根据验证结果,生成JWT Token并返回给客户端。
  • 设计思路
    • 使用JWT(JSON Web Token)实现无状态的用户认证,提升系统安全性。
    • 使用Session存储用户信息,便于后续操作时快速获取用户信息。
    • 使用MD5加密用户密码,保证用户密码的安全性。
@PostMapping("/login")
public Result loginuser(HttpSession session, @RequestBody User loginRequest){
    // 登录逻辑处理
}

二)领养申请管理

  • 代码实现逻辑
    • AdoptController中的apply方法接收用户的领养申请。
    • 通过JwtUtil从Token中提取用户ID,验证用户是否已登录。
    • 检查宠物是否存在且状态为“待领养”。
    • 检查用户是否已经申请过该宠物。
    • 更新宠物状态为“申请中”,并保存领养申请记录。
    • approvereject方法用于管理员或宠物发布者审核领养申请。
    • 审核通过时,更新申请状态为“已通过”,并更新宠物状态为“已领养”。
    • 审核拒绝时,更新申请状态为“已拒绝”,并更新宠物状态为“待领养”。
  • 调用过程
    • 用户在领养申请页面提交申请。
    • AdoptControllerapply方法被触发,执行领养申请逻辑。
    • 审核人员在领养管理页面进行审核操作。
    • AdoptControllerapprovereject方法被触发,执行审核逻辑。
  • 设计思路
    • 使用事务管理确保领养申请和宠物状态更新的一致性。
    • 通过权限校验确保只有管理员或宠物发布者可以审核领养申请。
    • 使用状态机模式管理领养申请的不同状态(申请中、已通过、已拒绝、已取消)。
@PostMapping("/apply")
public Result apply(@RequestBody AdoptRecord adoptRecord, HttpServletRequest request) {
    // 领养申请逻辑处理
}

@PostMapping("/approve/{id}")
public Result approve(@PathVariable Integer id, @RequestBody(required = false) java.util.Map<String, String> params, HttpServletRequest request) {
    // 审核通过逻辑处理
}

@PostMapping("/reject/{id}")
public Result reject(@PathVariable Integer id, @RequestBody(required = false) java.util.Map<String, String> params, HttpServletRequest request) {
    // 审核拒绝逻辑处理
}

三)宠物信息管理

  • 代码实现逻辑
    • PetController中的createPet方法接收用户提交的宠物信息。
    • 通过JwtUtil从Token中提取用户ID,验证用户是否已登录。
    • 设置救助者ID、创建时间和更新时间。
    • updatePetdeletePet方法用于更新和删除宠物信息。
    • getPets方法用于分页查询宠物列表,支持多种筛选条件。
    • getRecommendedPets方法用于获取推荐宠物列表,默认为最新的5只待领养宠物。
  • 调用过程
    • 用户在宠物发布页面提交宠物信息。
    • PetControllercreatePet方法被触发,执行宠物信息发布逻辑。
    • 用户在宠物管理页面进行更新或删除操作。
    • PetControllerupdatePetdeletePet方法被触发,执行更新或删除逻辑。
    • 用户在宠物列表页面进行分页查询。
    • PetControllergetPets方法被触发,执行分页查询逻辑。
  • 设计思路
    • 使用分页查询减少一次性加载的数据量,提升性能。
    • 支持多种筛选条件,方便用户查找符合要求的宠物。
    • 使用事务管理确保宠物信息更新和删除的一致性。
@PostMapping
@RequireRole({User.ROLE_USER, User.ROLE_ADMIN})
public Result<Pet> createPet(@RequestBody Pet pet, @RequestHeader("Authorization") String token) {
    // 创建宠物信息逻辑处理
}

@PutMapping("/{id}")
@RequireRole({User.ROLE_USER, User.ROLE_ADMIN})
public Result<Pet> updatePet(@PathVariable Integer id, @RequestBody Pet pet, @RequestHeader("Authorization") String token) {
    // 更新宠物信息逻辑处理
}

@DeleteMapping("/{id}")
@RequireRole({User.ROLE_USER, User.ROLE_ADMIN})
public Result<Void> deletePet(@PathVariable Integer id, @RequestHeader("Authorization") String token) {
    // 删除宠物信息逻辑处理
}

@GetMapping("/list")
public Result<PageInfo<Pet>> getPets(
        @RequestParam(defaultValue = "1") Integer page,
        @RequestParam(defaultValue = "10") Integer size,
        @RequestParam(required = false) String type,
        @RequestParam(required = false) String breed,
        @RequestParam(required = false) String gender,
        @RequestParam(required = false) String minAgeStr,
        @RequestParam(required = false) String maxAgeStr,
        @RequestParam(required = false) Boolean isVaccinated,
        @RequestParam(required = false) Boolean isSterilized,
        @RequestParam(required = false) Integer state) {
    // 分页查询宠物列表逻辑处理
}

四)私信消息管理

  • 代码实现逻辑
    • MessageController中的initSession方法初始化私信会话。
    • 检查用户是否已登录,并验证当前用户是否是会话的参与者之一。
    • sendMessage方法用于发送私信。
    • getSessionList方法用于获取用户的私信会话列表。
    • getMessageList方法用于分页获取会话消息列表。
    • markAsRead方法用于标记消息为已读。
    • getUnreadCount方法用于获取用户的未读消息数。
  • 调用过程
    • 用户在聊天页面点击与另一个用户聊天,触发initSession方法。
    • 用户发送消息时,触发sendMessage方法。
    • 用户查看私信会话列表时,触发getSessionList方法。
    • 用户查看某一会话的消息列表时,触发getMessageList方法。
    • 用户标记消息为已读时,触发markAsRead方法。
    • 用户查看未读消息数时,触发getUnreadCount方法。
  • 设计思路
    • 使用会话机制管理私信,确保消息的有序性和一致性。
    • 分页查询减少一次性加载的数据量,提升性能。
    • 使用事务管理确保消息发送和会话更新的一致性。
@PostMapping("/session/init")
public Result initSession(@RequestBody Map<String, Object> params, HttpServletRequest request) {
    // 初始化会话逻辑处理
}

@PostMapping("/send")
public Result<Message> sendMessage(@RequestBody Map<String, Object> params) {
    // 发送私信逻辑处理
}

@GetMapping("/sessions")
public Result<List<MessageSession>> getSessionList(@RequestParam Integer userId) {
    // 获取会话列表逻辑处理
}

@GetMapping("/list")
public Result<List<Message>> getMessageList(
        @RequestParam Integer sessionId,
        @RequestParam(defaultValue = "1") Integer pageNum,
        @RequestParam(defaultValue = "20") Integer pageSize) {
    // 获取会话消息列表逻辑处理
}

@PostMapping("/session/{sessionId}/read")
public Result<Void> markAsRead(
        @PathVariable Integer sessionId,
        @RequestParam Integer userId) {
    // 标记消息为已读逻辑处理
}

@GetMapping("/unread/count")
public Result<Integer> getUnreadCount(@RequestParam Integer userId) {
    // 获取未读消息数逻辑处理
}

五)博客管理

  • 代码实现逻辑
    • BlogController中的createBlog方法接收用户提交的博客信息。
    • 通过JwtUtil从Token中提取用户ID,验证用户是否已登录。
    • 设置作者ID、创建时间和更新时间。
    • updateBlogdeleteBlog方法用于更新和删除博客信息。
    • getLatestBlogs方法用于获取最新的博客列表。
    • getBlogById方法用于获取指定ID的博客详情。
  • 调用过程
    • 用户在博客发布页面提交博客信息。
    • BlogControllercreateBlog方法被触发,执行博客信息发布逻辑。
    • 用户在博客管理页面进行更新或删除操作。
    • BlogControllerupdateBlogdeleteBlog方法被触发,执行更新或删除逻辑。
    • 用户在博客列表页面查看最新博客。
    • BlogControllergetLatestBlogs方法被触发,执行最新博客查询逻辑。
    • 用户在博客详情页面查看指定ID的博客详情。
    • BlogControllergetBlogById方法被触发,执行博客详情查询逻辑。
  • 设计思路
    • 使用分页查询减少一次性加载的数据量,提升性能。
    • 支持多种查询条件,方便用户查找感兴趣的博客。
    • 使用事务管理确保博客信息更新和删除的一致性。
@PostMapping
public Result<Blog> createBlog(@RequestBody Blog blog, @RequestHeader("Authorization") String token) {
    // 创建博客逻辑处理
}

@PutMapping("/{id}")
@RequireRole({User.ROLE_USER, User.ROLE_ADMIN})
public Result<Blog> updateBlog(@PathVariable Integer id, @RequestBody Blog blog, @RequestHeader("Authorization") String token) {
    // 更新博客逻辑处理
}

@DeleteMapping("/{id}")
@RequireRole({User.ROLE_USER, User.ROLE_ADMIN})
public Result<Void> deleteBlog(@PathVariable Integer id, @RequestHeader("Authorization") String token) {
    // 删除博客逻辑处理
}

@GetMapping("/latest")
public Result getLatestBlogs() {
    // 获取最新博客逻辑处理
}

@GetMapping("/{id}")
public Result<Blog> getBlogById(@PathVariable Integer id) {
    // 获取博客详情逻辑处理
}

六)评论管理

  • 代码实现逻辑
    • CommentController中的addPetCommentaddBlogComment方法接收用户提交的评论信息。
    • 通过JwtUtil从Token中提取用户ID,验证用户是否已登录。
    • 设置评论的宠物ID或博客ID、用户ID、创建时间和更新时间。
    • getPetCommentsgetBlogComments方法用于分页查询宠物或博客的评论列表。
    • deleteComment方法用于删除评论。
    • likeCommentunlikeComment方法用于点赞或取消点赞评论。
    • checkLike方法用于检查用户是否已点赞。
  • 调用过程
    • 用户在宠物或博客详情页面提交评论。
    • CommentControlleraddPetCommentaddBlogComment方法被触发,执行评论发布逻辑。
    • 用户在评论列表页面查看评论。
    • CommentControllergetPetCommentsgetBlogComments方法被触发,执行评论查询逻辑。
    • 用户删除自己的评论时,触发deleteComment方法。
    • 用户点赞或取消点赞评论时,触发likeCommentunlikeComment方法。
    • 用户查看自己是否已点赞时,触发checkLike方法。
  • 设计思路
    • 使用分页查询减少一次性加载的数据量,提升性能。
    • 支持多种查询条件,方便用户查找感兴趣的评论。
    • 使用事务管理确保评论信息更新和删除的一致性。
@PostMapping("/pets/{petId}/comments")
public Result<Comment> addPetComment(
        @PathVariable Integer petId,
        @RequestBody Comment comment,
        @RequestHeader("Authorization") String token) {
    // 发布宠物评论逻辑处理
}

@PostMapping("/blogs/{blogId}/comments")
public Result<Comment> addBlogComment(
        @PathVariable Integer blogId,
        @RequestBody Comment comment,
        @RequestHeader("Authorization") String token) {
    // 发布博客评论逻辑处理
}

@GetMapping("/pets/{petId}/comments")
public Result<Page<Comment>> getPetComments(
        @PathVariable Integer petId,
        @RequestParam(defaultValue = "1") Integer page,
        @RequestParam(defaultValue = "10") Integer size,
        @RequestHeader(value = "Authorization", required = false) String token) {
    // 获取宠物评论逻辑处理
}

@GetMapping("/blogs/{blogId}/comments")
public Result<Page<Comment>> getBlogComments(
        @PathVariable Integer blogId,
        @RequestParam(defaultValue = "1") Integer page,
        @RequestParam(defaultValue = "10") Integer size,
        @RequestHeader(value = "Authorization", required = false) String token) {
    // 获取博客评论逻辑处理
}

@DeleteMapping("/comments/{id}")
public Result<Boolean> deleteComment(
        @PathVariable Integer id,
        @RequestHeader("Authorization") String token) {
    // 删除评论逻辑处理
}

@PostMapping("/pets/{petId}/comments/{commentId}/like")
public Result likeComment(
        @PathVariable Integer petId,
        @PathVariable Integer commentId,
        @RequestHeader("Authorization") String token) {
    // 点赞评论逻辑处理
}

@DeleteMapping("/pets/{petId}/comments/{commentId}/like")
public Result unlikeComment(
        @PathVariable Integer petId,
        @PathVariable Integer commentId,
        @RequestHeader("Authorization") String token) {
    // 取消点赞评论逻辑处理
}

@GetMapping("/pets/{petId}/comments/{commentId}/like")
public Result checkLike(
        @PathVariable Integer petId,
        @PathVariable Integer commentId,
        @RequestHeader("Authorization") String token) {
    // 检查是否已点赞逻辑处理
}

六、数据库表结构以及表关系梳理

这些表通过外键关系相互连接,构成了一个能够支持宠物领养系统的数据结构。每个表都有其特定的角色和功能,并且通过外键与其他表建立关系,确保了数据的完整性和一致性。

一)user

  • 作用:存储用户信息,包括普通用户和管理员。
  • 结构
    • id:主键,自增,唯一标识一个用户。
    • userName:用户名。
    • password:用户密码,存储为MD5加密后的字符串。
    • realName:真实姓名。
    • telephone:电话号码。
    • email:电子邮件地址。
    • birthday:出生日期。
    • sex:性别,通常用’M’表示男性,'F’表示女性。
    • pic:用户头像路径。
    • role:用户角色,如USERADMIN
    • openId:微信登录用户的唯一标识符。
    • location:用户位置信息。
    • description:用户描述或简介。
    • status:用户状态,0表示禁用,1表示启用。
    • lastLoginTime:最后一次登录时间。
    • createTime:创建时间。
    • updateTime:更新时间。

二)pet

  • 作用:存储宠物信息,包括宠物的基本属性和状态。
  • 结构
    • id:主键,自增,唯一标识一个宠物。
    • petName:宠物名字。
    • petType:宠物类型,如猫、狗等。
    • petSubType:宠物子类型,如品种。
    • breed:宠物品种。
    • sex:宠物性别,通常用’M’表示雄性,'F’表示雌性。
    • weight:宠物体重。
    • birthday:宠物生日。
    • isVaccinated:是否已接种疫苗,布尔值。
    • isSterilized:是否已绝育,布尔值。
    • healthStatus:健康状况描述。
    • pic:宠物图片路径,多个图片用逗号分隔。
    • state:宠物状态,如待领养、申请中、已领养。
    • remark:备注信息。
    • rescuerId:救助者ID,关联到user
    • viewCount:浏览次数,默认为0。
    • favoriteCount:收藏次数,默认为0。
    • createTime:创建时间。
    • updateTime:更新时间。

三)adopt_record

  • 作用:存储领养申请记录,记录用户对宠物的领养申请情况。
  • 结构
    • id:主键,自增,唯一标识一条领养申请记录。
    • userId:申请人ID,关联到user
    • petId:申请领养的宠物ID,关联到pet
    • state:申请状态,如申请中、已通过、已拒绝、已取消。
    • remark:备注信息。
    • reviewComment:审核意见。
    • reviewerId:审核人ID,关联到user
    • reviewTime:审核时间。
    • createTime:创建时间。
    • updateTime:更新时间。

四)favorite

  • 作用:存储用户对宠物的收藏记录。
  • 结构
    • id:主键,自增,唯一标识一条收藏记录。
    • userId:收藏者ID,关联到user
    • petId:被收藏的宠物ID,关联到pet
    • createTime:创建时间。
    • updateTime:更新时间。

五)blog

  • 作用:存储博客文章信息。
  • 结构
    • id:主键,自增,唯一标识一篇博客。
    • title:博客标题。
    • content:博客内容。
    • authorId:作者ID,关联到user
    • coverImage:封面图片路径。
    • category:分类。
    • tags:标签,多个标签用逗号分隔。
    • viewCount:浏览次数,默认为0。
    • likeCount:点赞次数,默认为0。
    • favoriteCount:收藏次数,默认为0。
    • commentCount:评论次数,默认为0。
    • status:博客状态,如草稿、已发布。
    • createTime:创建时间。
    • updateTime:更新时间。

六)blog_favorite

  • 作用:存储用户对博客的收藏记录。
  • 结构
    • id:主键,自增,唯一标识一条收藏记录。
    • blogId:被收藏的博客ID,关联到blog
    • userId:收藏者ID,关联到user
    • createTime:创建时间。
    • updateTime:更新时间。

七)comment

  • 作用:存储评论信息,包括对宠物和博客的评论。
  • 结构
    • id:主键,自增,唯一标识一条评论。
    • petId:评论的宠物ID,关联到pet,允许为空。
    • blogId:评论的博客ID,关联到blog,允许为空。
    • userId:评论者ID,关联到user
    • parentId:父评论ID,用于构建评论的层级结构,允许为空。
    • content:评论内容。
    • likeCount:点赞次数,默认为0。
    • replyCount:回复次数,默认为0。
    • createTime:创建时间。
    • updateTime:更新时间。
    • isLiked:当前用户是否已点赞,布尔值,默认为false。
    • likes:点赞数,默认为0。

八)answer

  • 作用:存储评论的回复信息。
  • 结构
    • id:主键,自增,唯一标识一条回复。
    • userId:回复者ID,关联到user
    • commentId:回复的评论ID,关联到comment
    • replyId:回复的目标回复ID,用于构建多级回复结构,允许为空。
    • content:回复内容。
    • likeCount:点赞次数,默认为0。
    • createTime:创建时间。
    • updateTime:更新时间。

九)comment_like

  • 作用:存储评论的点赞记录。
  • 结构
    • id:主键,自增,唯一标识一条点赞记录。
    • commentId:点赞的评论ID,关联到comment
    • userId:点赞者ID,关联到user
    • createTime:创建时间。
    • updateTime:更新时间。

十)message_session

  • 作用:存储私信会话信息。
  • 结构
    • id:主键,自增,唯一标识一会话。
    • userId1:会话参与者1的ID,关联到user
    • userId2:会话参与者2的ID,关联到user
    • petId:关联的宠物ID,关联到pet,允许为空。
    • lastMessage:最近一条消息内容。
    • lastMessageTime:最近一条消息的时间。
    • unreadCount1:用户1的未读消息数。
    • unreadCount2:用户2的未读消息数。
    • createTime:创建时间。
    • updateTime:更新时间。

十一)message

  • 作用:存储私信消息内容。
  • 结构
    • id:主键,自增,唯一标识一条消息。
    • sessionId:会话ID,关联到message_session
    • senderId:发送者ID,关联到user
    • content:消息内容。
    • isRead:消息是否已读,布尔值,默认为false。
    • createTime:创建时间。
    • updateTime:更新时间。

十二)banner

  • 作用:存储轮播图信息。
  • 结构
    • id:主键,自增,唯一标识一张轮播图。
    • imageUrl:轮播图图片URL。
    • linkUrl:点击轮播图跳转的链接。
    • sort:排序字段,默认为0。
    • isActive:是否激活,0表示未激活,1表示激活。
    • createTime:创建时间。
    • updateTime:更新时间。

十三)表和表的关系

  • adopt_record表通过userIdpetId分别与user表和pet表关联,表示用户对宠物的领养申请。
  • favorite表通过userIdpetId分别与user表和pet表关联,表示用户对宠物的收藏。
  • blog表通过authorIduser表关联,表示博客的作者。
  • blog_favorite表通过blogIduserId分别与blog表和user表关联,表示用户对博客的收藏。
  • comment表通过userIdpetIdblogId分别与user表、pet表和blog表关联,表示用户对宠物或博客的评论。
  • answer表通过userIdcommentId分别与user表和comment表关联,表示用户对评论的回复。
  • comment_like表通过commentIduserId分别与comment表和user表关联,表示用户对评论的点赞。
  • message_session表通过userId1userId2petId分别与user表和pet表关联,表示两个用户之间的私信会话,可能涉及某只宠物。
  • message表通过sessionIdsenderId分别与message_session表和user表关联,表示私信会话中的具体消息。

这些表之间通过外键建立了紧密的联系,确保了数据的一致性和完整性,共同支撑了宠物领养系统的各项功能,如用户管理、宠物信息管理、领养申请、收藏、评论、私信等功能模块。

七、可能的答辩问题

一)如何实现用户的登录和注册功能?

解答:用户登录和注册功能由UserController中的loginuseradd方法实现。用户提交用户名和密码后,系统通过UserService中的loginuser方法验证用户信息。注册功能通过add方法验证用户名是否已存在,并设置默认头像和角色。登录成功后,生成JWT Token并通过HttpSession保存用户信息。

代码片段

@PostMapping("/login")
public Result loginuser(HttpSession session, @RequestBody User loginRequest){
    // 登录逻辑
}

@PostMapping("/register")
public Result add(@RequestBody User user){
    // 注册逻辑
}

二)系统如何处理并发用户登录?

解答:系统使用JwtUtil生成和验证JWT Token来管理用户会话,每个用户登录后都会生成一个唯一的Token。Token的生成和验证是线程安全的,确保多个用户可以同时登录而不会产生冲突。

代码片段

public static String generateToken(User user) {
    // 生成Token逻辑
}

三)系统如何进行权限控制?

解答:权限控制通过自定义注解@RequireRole和拦截器RoleInterceptor实现。@RequireRole用于标注需要特定角色才能访问的API,RoleInterceptor在请求到达控制器之前检查用户是否有权限访问。

代码片段

@RequireRole({User.ROLE_USER, User.ROLE_ADMIN})
public Result updatePet(@PathVariable Integer id, @RequestBody Pet pet, @RequestHeader("Authorization") String token) {
    // 更新宠物逻辑
}

四)领养申请功能是如何实现的?

解答:领养申请功能通过AdoptController实现,用户提交申请时,系统调用AdoptServiceapply方法验证宠物状态和用户是否已申请。申请成功后,宠物状态变为“申请中”。

代码片段

@PostMapping("/apply")
public Result apply(@RequestBody AdoptRecord adoptRecord, HttpServletRequest request) {
    // 申请逻辑
}

五)系统如何处理领养申请的审批流程?

解答:领养申请的审批流程通过AdoptController中的approvereject方法实现。管理员或宠物发布者可以审核申请,审核通过或拒绝时,更新申请状态和宠物状态,并记录审核信息。

代码片段

@PostMapping("/approve/{id}")
public Result approve(@PathVariable Integer id, @RequestBody(required = false) java.util.Map<String, String> params, HttpServletRequest request) {
    // 审核通过逻辑
}

@PostMapping("/reject/{id}")
public Result reject(@PathVariable Integer id, @RequestBody(required = false) java.util.Map<String, String> params, HttpServletRequest request) {
    // 审核拒绝逻辑
}

六)系统如何实现微信登录功能?

解答:微信登录通过UserController中的wxLogin方法实现。用户通过微信授权获取code,系统调用微信接口获取用户信息,创建或更新用户记录,并生成JWT Token。

代码片段

@PostMapping("/wx-login")
public Result wxLogin(@RequestBody Map<String, Object> loginRequest, HttpSession session) {
    // 微信登录逻辑
}

七)系统如何处理文件上传和下载?

解答:文件上传和下载通过FileLoad工具类实现。上传文件时,系统根据文件类型选择不同的上传目录,并返回文件的访问路径。下载文件时,系统根据文件路径删除文件。

代码片段

public static String uploadPetPic(MultipartFile file) throws IOException {
    // 上传宠物图片逻辑
}

public static boolean deleteFile(String filePath) {
    // 删除文件逻辑
}

八)系统如何实现分页查询功能?

解答:分页查询功能通过PageHelper插件实现。在查询数据前,使用PageHelper.startPage方法设置分页参数,查询完成后,返回PageInfo对象封装分页结果。

代码片段

PageHelper.startPage(page, size);
List<Pet> pets = petMapper.findPets(type, breed, gender, minAge, maxAge, isVaccinated, isSterilized, state);
return new PageInfo<>(pets);

九)系统如何实现评论和回复功能?

解答:评论和回复功能通过CommentControllerAnswerController实现。用户发表评论时,系统调用CommentServiceaddComment方法保存评论;回复评论时,系统调用AnswerServiceaddAnswer方法保存回复。评论和回复都支持点赞和取消点赞。

代码片段

@PostMapping("/pets/{petId}/comments")
public Result<Comment> addPetComment(
        @PathVariable Integer petId,
        @RequestBody Comment comment,
        @RequestHeader("Authorization") String token) {
    // 发表宠物评论逻辑
}

@PostMapping("/pets/{petId}/comments/{commentId}/like")
public Result likeComment(
        @PathVariable Integer petId,
        @PathVariable Integer commentId,
        @RequestHeader("Authorization") String token) {
    // 点赞评论逻辑
}

十)系统如何处理异常情况?

解答:系统通过全局异常处理器GlobalExceptionHandler捕获所有未处理的异常,并返回统一的Result对象给客户端。针对特定类型的异常,如AuthorizationExceptionBusinessException,有不同的处理逻辑。

代码片段

@ControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(Exception.class)
    @ResponseBody
    public Result handleException(Exception e) {
        // 异常处理逻辑
    }
}

十一)系统如何实现数据分页和排序?

解答:数据分页和排序通过PageHelper插件和MyBatis的动态SQL实现。在查询数据前,使用PageHelper.startPage方法设置分页参数,并在SQL中添加排序条件。查询完成后,返回PageInfo对象封装分页和排序结果。

代码片段

PageHelper.startPage(page, size);
List<Blog> blogs = blogMapper.findRecentBlogs(limit);
return new PageInfo<>(blogs);

十二)系统如何实现私信消息功能?

解答:私信消息功能通过MessageControllerMessageService实现。用户发起私信时,系统调用MessageServicesendMessage方法保存消息,并更新会话状态。用户可以通过MessageControllergetMessageList方法获取会话消息列表。

代码片段

@PostMapping("/send")
public Result<Message> sendMessage(@RequestBody Map<String, Object> params) {
    // 发送私信逻辑
}

@GetMapping("/list")
public Result<List<Message>> getMessageList(
        @RequestParam Integer sessionId,
        @RequestParam(defaultValue = "1") Integer pageNum,
        @RequestParam(defaultValue = "20") Integer pageSize) {
    // 获取会话消息列表逻辑
}

十三)系统如何处理数据一致性和事务管理?

解答:系统通过Spring的@Transactional注解实现事务管理,确保多个数据库操作要么全部成功,要么全部回滚。对于涉及多表操作的场景,如领养申请审批,系统会在同一个事务中更新多个表的数据,确保数据一致性。

代码片段

@Transactional
public boolean apply(AdoptRecord adoptRecord) {
    // 领养申请逻辑
}

十四)系统如何实现跨域资源共享(CORS)?

解答:系统通过WebConfig类中的addCorsMappings方法配置CORS,允许来自任意域名的请求。配置了允许的HTTP方法、请求头和最大缓存时间,确保前后端分离项目可以正常通信。

代码片段

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("*")
                .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
                .allowedHeaders("*")
                .maxAge(3600);
    }
}

十五)系统如何实现用户收藏和取消收藏功能?

解答:用户收藏和取消收藏功能通过FavoriteControllerBlogFavoriteController实现。用户收藏宠物或博客时,系统调用FavoriteServiceBlogFavoriteServiceaddFavorite方法保存收藏记录;取消收藏时,调用removeFavorite方法删除记录,并更新收藏数量。

代码片段

@PostMapping("/add/{petId}")
public Result addFavorite(@PathVariable Integer petId, HttpServletRequest request) {
    // 收藏宠物逻辑
}

@PostMapping("/remove/{petId}")
public Result removeFavorite(@PathVariable Integer petId, HttpServletRequest request) {
    // 取消收藏逻辑
}

十六)系统如何实现轮播图管理功能?

解答:轮播图管理功能通过BannerControllerBannerService实现。管理员可以添加、删除和更新轮播图,系统通过BannerMapper与数据库交互,保存或查询轮播图信息。

代码片段

@PostMapping
public Result<?> saveBanner(@RequestBody Banner banner) {
    // 保存轮播图逻辑
}

@DeleteMapping("/{id}")
public Result<?> deleteBanner(@PathVariable Integer id) {
    // 删除轮播图逻辑
}

十七)系统如何实现博客文章的发布和管理?

解答:博客文章的发布和管理通过BlogControllerBlogService实现。用户提交博客内容时,系统调用BlogServicecreateBlog方法保存博客,并设置默认状态为草稿。管理员可以审核博客,将其状态更改为已发布。

代码片段

@PostMapping
public Result<Blog> createBlog(@RequestBody Blog blog, @RequestHeader("Authorization") String token) {
    // 创建博客逻辑
}

@PutMapping("/{id}")
public Result<Blog> updateBlog(@PathVariable Integer id, @RequestBody Blog blog, @RequestHeader("Authorization") String token) {
    // 更新博客逻辑
}

十八)系统如何实现用户信息的修改和密码重置?

解答:用户信息的修改和密码重置通过UserController中的updateresetPassword方法实现。用户修改信息时,系统调用UserServiceupdate方法更新用户记录;重置密码时,系统验证用户名和手机号,并更新用户密码。

代码片段

@PostMapping("/update")
public Result update(@RequestBody User user){
    // 更新用户信息逻辑
}

@PostMapping("/reset-password")
public Result resetPassword(@RequestBody Map<String, String> request) {
    // 重置密码逻辑
}

十九)系统如何实现图片上传和展示?

解答:图片上传和展示通过FileLoad工具类和各控制器中的上传接口实现。用户上传图片时,系统根据图片类型选择不同的上传目录,并返回图片的访问路径。展示图片时,系统根据图片路径从服务器读取图片并返回给前端。

代码片段

public static String uploadPetPic(MultipartFile file) throws IOException {
    // 上传宠物图片逻辑
}

public static String uploadBlogImage(MultipartFile file) throws IOException {
    // 上传博客图片逻辑
}

二十)系统如何实现统计数据功能?

解答:统计数据功能通过StatsController实现。管理员可以查询用户总数、宠物总数、领养申请总数和评论总数。系统调用相应服务类的方法,统计不同类型的记录数量,并返回给前端。

代码片段

@GetMapping("/user/count")
@RequireRole(User.ROLE_ADMIN)
public Result<Integer> getUserCount() {
    // 获取用户总数逻辑
}

@GetMapping("/adopt/count")
@RequireRole(User.ROLE_ADMIN)
public Result<Integer> getAdoptCount() {
    // 获取领养申请总数逻辑
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2315970.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于SpringBoot的“考研互助平台”的设计与实现(源码+数据库+文档+PPT)

基于SpringBoot的“考研互助平台”的设计与实现&#xff08;源码数据库文档PPT) 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SpringBoot 工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 系统整体功能图 局部E-R图 系统首页界面 系统注册…

基于javaweb的SpringBoot足球俱乐部管理系统设计与实现(源码+文档+部署讲解)

技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;免费功能设计、开题报告、任务书、中期检查PPT、系统功能实现、代码编写、论文编写和辅导、论…

DQN 玩 2048 实战|第一期!搭建游戏环境(附 PyGame 可视化源码)

视频讲解&#xff1a; DQN 玩 2048 实战&#xff5c;第一期&#xff01;搭建游戏环境&#xff08;附 PyGame 可视化源码&#xff09; 代码仓库&#xff1a;GitHub - LitchiCheng/DRL-learning: 深度强化学习 2048游戏介绍&#xff0c;引用维基百科 《2048》在44的网格上进行。…

高频面试题(含笔试高频算法整理)基本总结回顾24

干货分享&#xff0c;感谢您的阅读&#xff01; &#xff08;暂存篇---后续会删除&#xff0c;完整版和持续更新见高频面试题基本总结回顾&#xff08;含笔试高频算法整理&#xff09;&#xff09; 备注&#xff1a;引用请标注出处&#xff0c;同时存在的问题请在相关博客留言…

第八节:红黑树(初阶)

【本节要点】 红黑树概念红黑树性质红黑树结点定义红黑树结构红黑树插入操作的分析 一、红黑树的概念与性质 1.1 红黑树的概念 红黑树 &#xff0c;是一种 二叉搜索树 &#xff0c;但 在每个结点上增加一个存储位表示结点的颜色&#xff0c;可以是 Red和 Black 。 通过对 任何…

Webpack vs Rollup vs Parcel:构建工具深度对比

文章目录 1. 核心特性对比1.1 功能定位1.2 技术架构对比 2. 配置与使用2.1 Webpack 配置示例2.2 Rollup 配置示例2.3 Parcel 使用示例 3. 性能对比3.1 构建速度3.2 输出质量 4. 生态系统4.1 插件生态4.2 学习曲线 5. 适用场景分析5.1 Webpack 适用场景5.2 Rollup 适用场景5.3 P…

Centos7使用docker搭建redis集群

前置准备&#xff1a; Centos7安装docker就不多说了… 本次目的是搭建3主3从&#xff08;当然你也可以按需扩展&#xff09;准备三台服务器&#xff0c;假定IP分别为&#xff1a;192.168.75.128、192.168.75.129、192.168.75.130安装 redis&#xff1a; #拉取redis docker p…

数字孪生像魔镜,映照出无限可能的未来

在当今科技飞速发展的时代&#xff0c;数字孪生作为一项极具潜力的前沿技术&#xff0c;正逐渐崭露头角&#xff0c;成为众多领域关注的焦点。它犹如一面神奇的魔镜&#xff0c;以数字化的方式精准映照出现实世界中的各种实体与系统&#xff0c;为我们开启了一扇通往无限可能未…

前端知识点---原型-原型链(javascript)

文章目录 原型原型链:实际应用面试题回答 原型 原型:每个函数都有prototype属性 称之为原型 因为这个属性的值是个对象&#xff0c;也称为原型对象 只有函数才有prototype属性 作用: 1.存放一些属性和方法 2.在Javascript中实现继承 const arr new Array(1, 2, 3, 4) con…

数据类设计_图片类设计之6_混合图形类设计(前端架构)

前言 学的东西多了,要想办法用出来.C和C是偏向底层的语言,直接与数据打交道.尝试做一些和数据方面相关的内容 引入 接续上一篇,讨论混合图形类设计 方法论-现在能做什么 这段属于聊天内容---有句话是这么说的&#xff1a;不要只埋头拉车&#xff0c;还要抬头看路。写代码也是…

2024年12月CCF-GESP编程能力等级认证C++编程一级真题解析

一级真题的难度: ‌ CCF-GESP编程能力等级认证C++编程一级真题的难度适中‌。这些真题主要考察的是C++编程的基础知识、基本语法以及简单的算法逻辑。从搜索结果中可以看到,真题内容包括了选择题、编程题等题型,涉及的内容如C++表达式的计算、基本输入输出语句的理解…

尤瓦尔·诺亚·赫拉利(Yuval Noah Harari)作品和思想深度报告

尤瓦尔诺亚赫拉利&#xff08;Yuval Noah Harari&#xff09;作品和思想深度报告 引言 尤瓦尔诺亚赫拉利&#xff08;Yuval Noah Harari&#xff09;是当今最具影响力的公众知识分子之一 ynharari.com 。作为一名历史学家和哲学家&#xff0c;他以宏大的视角和清晰生动的语言…

JConsole:JDK性能监控利器之JConsole的使用说明与案例实践

&#x1fa81;&#x1f341; 希望本文能给您带来帮助&#xff0c;如果有任何问题&#xff0c;欢迎批评指正&#xff01;&#x1f405;&#x1f43e;&#x1f341;&#x1f425; 文章目录 一、背景二、JConsole的启动与连接2.1 JConsole的启动2.2 进程连接2.2.1 本地进程连接2.2…

Neural Architecture Search for Transformers:A Survey

摘要 基于 Transformer 的深度神经网络架构因其在自然语言处理 (NLP) 和计算机视觉 (CV) 领域的各种应用中的有效性而引起了极大的兴趣。这些模型是多种语言任务&#xff08;例如情绪分析和文本摘要&#xff09;的实际选择&#xff0c;取代了长短期记忆 (LSTM) 模型。视觉 Tr…

Browser Copilot 开源浏览器扩展,使用现有或定制的 AI 助手来完成日常 Web 应用程序任务。

一、软件介绍 文末提供源码和开源扩展程序下载 Browser Copilot 是一个开源浏览器扩展&#xff0c;允许您使用现有或定制的 AI 助手来帮助您完成日常 Web 应用程序任务。 目标是提供多功能的 UI 和简单的框架&#xff0c;以实现和使用越来越多的 copilots&#xff08;AI 助手&…

基于PSO粒子群优化的XGBoost时间序列预测算法matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 4.1 XGBoost算法原理 4.2 XGBoost优化 5.算法完整程序工程 1.算法运行效果图预览 (完整程序运行后无水印) 2.算法运行软件版本 matlab2024b 3.部分核心程序 &#xff08;完整版代码包含…

aws(学习笔记第三十三课) 深入使用cdk 练习aws athena

文章目录 aws(学习笔记第三十三课) 深入使用cdk学习内容&#xff1a;1. 使用aws athena1.1 什么是aws athena1.2 什么是aws glue1.2 为什么aws athena和aws glue一起使用 2. 开始练习aws athena2.1 代码链接2.2 整体架构2.3 代码解析2.3.1 创建测试数据的S3 bucket2.3.2 创建保…

基于RAGFlow本地部署DeepSpeek-R1大模型与知识库:从配置到应用的全流程解析

作者&#xff1a;后端小肥肠 &#x1f34a; 有疑问可私信或评论区联系我。 &#x1f951; 创作不易未经允许严禁转载。 姊妹篇&#xff1a; DeepSpeek服务器繁忙&#xff1f;这几种替代方案帮你流畅使用&#xff01;&#xff08;附本地部署教程&#xff09;-CSDN博客 10分钟上手…

SpringBoot 集成logback日志链路追踪

项目场景 有时候一个业务调用链场景&#xff0c;很长&#xff0c;调了各种各样的方法&#xff0c;看日志的时候&#xff0c;各个接口的日志穿插&#xff0c;确实让人头大。 为了解决这个痛点&#xff0c;就使用了 TraceId&#xff0c;根据 TraceId 关键字进入服务器查询日志中…

【Python办公】Excel通用匹配工具(双表互匹)

目录 专栏导读1、背景介绍2、库的安装3、核心代码4、完整代码总结专栏导读 🌸 欢迎来到Python办公自动化专栏—Python处理办公问题,解放您的双手 🏳️‍🌈 博客主页:请点击——> 一晌小贪欢的博客主页求关注 👍 该系列文章专栏:请点击——>Python办公自动化专…