最近我用springBoot开发了一个二手交易管理系统,分享一下实现方式~

最近把java升级到了java21的版本,就使用springboot框架写了一个二手交易平台来熟悉一下springBoot框架的语法。功能方面主要做了管理后台和手机用户端的功能。

管理系统后台的样式:

手机端:

主要做的功能有:

(1)平台管理端

· 用户管理

· 商品分类管理

· 二手商品管理

· 订单管理

· 管理员管理

· 资讯管理

(2)用户端

· 二手商品展示

· 分类筛选

· 商品详情

· 订单凭证与我的订单

· 联系卖家

· 资讯查看

· 我的发布

主要练习的后端技术是:springboot2.7 + mybatisplus

使用的数据库:mysql8.0

前端使用的 vue2 + Element UI

手机端使用的是 uni-app框架 也是可以打包小程序的!

node版本:16.20

后端管理员管理部分代码:

bash 复制代码
package com.jsonll.base.controller;
import com.jsonll.base.core.NoLogin;
import com.jsonll.base.core.R;
import com.jsonll.base.entity.Admin;
import com.jsonll.base.request.AdminRequest;
import com.jsonll.base.service.IAdminService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

/**
 * 管理员控制器
 */
@RestController
@RequestMapping("/admin")
public class AdminController extends BaseController {
    @Autowired
    private IAdminService iAdminService;

    /**
     * 管理员登录
     */
    @PostMapping("/login")
    @NoLogin
    public R login(@RequestBody AdminRequest request) {
        return iAdminService.login(request);
    }

    /**
     * RESTful: 获取管理员列表(不分页)
     */
    @GetMapping("")
    public R list() {
        // 直接使用 MyBatis-Plus 提供的 list 方法
        java.util.List<Admin> list = iAdminService.list();
        // 隐藏密码
        list.forEach(item -> item.setPassword(null));
        return R.successData(list);
    }

    /**
     * RESTful: 获取单个管理员详情
     */
    @GetMapping("/{id}")
    public R get(@PathVariable Integer id) {
        Admin admin = iAdminService.getById(id);
        if (admin == null) {
            return R.error("管理员不存在");
        }
        admin.setPassword(null);
        return R.successData(admin);
    }

    /**
     * 获取管理员列表
     */
    @GetMapping("/pageList")
    public R pageList(AdminRequest adminRequest) {
        return iAdminService.pageList(adminRequest);
    }

    /**
     * 添加管理员
     */
    @PostMapping("")
    public R add(@RequestBody Admin admin) {
        return iAdminService.add(admin);
    }

    /**
     * 更新管理员信息
     */
    @PutMapping("/{id}")
    public R updateAdmin(@PathVariable Integer id, @RequestBody Admin admin) {
        // 路径参数为准
        admin.setId(id);
        return iAdminService.updateAdmin(admin);
    }

    /**
     * 删除管理员
     */
    @DeleteMapping("/{id}")
    public R deleteAdmin(@PathVariable Integer id) {
        return iAdminService.deleteAdmin(id);
    }


    /**
     * 获取当前登录用户信息
     */
    @GetMapping("/userInfo")
    public R userInfo() {
        Integer userId = getLoginUserId();
        return iAdminService.userInfo(userId);
    }
}
bash 复制代码
package com.jsonll.base.service;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jsonll.base.core.R;
import com.jsonll.base.entity.Admin;
import com.jsonll.base.mapper.AdminMapper;
import com.jsonll.base.request.AdminRequest;
import com.jsonll.base.utils.JwtHelper;
import com.jsonll.base.utils.MD5;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.List;

/**
 * 管理员表 接口实现类
 */
@Service
public class AdminServiceImpl extends ServiceImpl<AdminMapper, Admin> implements IAdminService {

    @Override
    public R login(AdminRequest request) {
        // 参数校验
        if (StringUtils.isEmpty(request.getUsername()) || StringUtils.isEmpty(request.getPassword())) {
            return R.error("用户名或密码不能为空");
        }
        // 查询条件 示例
        LambdaQueryWrapper<Admin> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Admin::getUsername, request.getUsername());
        Admin admin = getOne(queryWrapper);

        // 用户不存在
        if (admin == null) {
            return R.error( "用户名或密码错误");
        }

        // 密码校验
        String encryptedPassword = MD5.encoder(request.getPassword());
        if (!admin.getPassword().equals(encryptedPassword)) {
            return R.error( "用户名或密码错误");
        }

        // 生成token
        String token = JwtHelper.sign(admin.getId());
        // 给实体类字段赋值
        admin.setToken(token);

        return R.successData(admin);
    }

    @Override
    public R userInfo(Integer userId) {
        //根据id查询示例
        Admin admin = getById(userId);
        if (admin == null) {
            return R.error("未查询到数据");
        }
        return R.successData(admin);
    }

    @Override
    public R pageList(AdminRequest adminRequest) {
        //分页示例
        Page<Admin> page = new Page<>(adminRequest.getPageNum() == null ? 1 : adminRequest.getPageNum(), adminRequest.getPageSize() == null ? 10 : adminRequest.getPageSize());
        QueryWrapper<Admin> queryWrapper = new QueryWrapper<>();
        //搜索条件 示例
        if(!StringUtils.isEmpty(adminRequest.getSearch())){
            queryWrapper.lambda().like(Admin::getUsername, adminRequest.getSearch());
        }
        page(page, queryWrapper.lambda().orderByDesc(Admin::getId));
        // 处理返回结果,隐藏密码
        List<Admin> records = page.getRecords();
        //列表回显 示例
        records.forEach(admin->{
             admin.setPassword(null);
             //列表实体类 回显示例
             admin.setEchoExample("回显示例");
        });
        return R.successData(page);
    }

    @Override
    public R add(Admin admin) {
        // 参数校验
        if (StringUtils.isEmpty(admin.getUsername()) || StringUtils.isEmpty(admin.getPassword())) {
            return R.error("用户名或密码不能为空");
        }

        // where条件示例
        LambdaQueryWrapper<Admin> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Admin::getUsername, admin.getUsername());
        if (count(queryWrapper) > 0) {
            return R.error( "用户名已存在");
        }
        // 设置默认值
        admin.setIsSuper(0); // 默认为普通管理员
        // 密码加密
        admin.setPassword(MD5.encoder(admin.getPassword()));
        // 保存管理员信息 示例
        save(admin);
        //无返回数据 示例
        return R.success();
    }

    @Override
    public R updateAdmin(Admin admin) {
        // 参数校验
        if (admin.getId() == null) {
            return R.error( "管理员ID不能为空");
        }

        // 获取原管理员信息
        Admin existAdmin = getById(admin.getId());
        if (existAdmin == null) {
            return R.error("管理员不存在");
        }

        // 不允许修改超级管理员状态
        if (existAdmin.getIsSuper() == 1) {
            admin.setIsSuper(1);
        }

        // 如果修改了用户名,检查是否已存在
        if (!StringUtils.isEmpty(admin.getUsername()) && !admin.getUsername().equals(existAdmin.getUsername())) {
            LambdaQueryWrapper<Admin> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(Admin::getUsername, admin.getUsername());
            if (count(queryWrapper) > 0) {
                return R.error( "用户名已存在");
            }
        }
        // 如果密码为空,使用原密码
        if (StringUtils.isEmpty(admin.getPassword())) {
            admin.setPassword(existAdmin.getPassword());
        } else {
            // 密码加密
            admin.setPassword(MD5.encoder(admin.getPassword()));
        }
        // 根据id更新管理员信息 示例
        updateById(admin);
        return R.success();
    }

    @Override
    public R deleteAdmin(Integer id) {
        // 参数校验
        if (id == null) {
            return R.error( "管理员ID不能为空");
        }
        // 获取管理员信息
        Admin admin = getById(id);
        if (admin == null) {
            return R.error("管理员不存在");
        }
        // 不允许删除超级管理员
        if (admin.getIsSuper() == 1) {
            return R.error( "不能删除超级管理员");
        }
        // 删除管理员 示例
        removeById(id);
        return R.success();
    }
}

手机端代码:

bash 复制代码
<template>
  <view class="my-container">
    <!-- 用户信息区域 -->
    <view class="user-info" v-if="isLogin">
      <view class="avatar-box">
        <image class="avatar" :src="userInfo.avatar || '/static/default-avatar.png'" mode="aspectFill"></image>
      </view>
      <view class="user-detail">
        <view class="nickname">{{userInfo.nickname || '未设置昵称'}}</view>
        <view class="auth-status">
          <text :class="['auth-tag', userInfo.auth_status === 1 ? 'auth-yes' : 'auth-no']">
            {{userInfo.auth_status === 1 ? '已认证' : '未认证'}}
          </text>
        </view>
      </view>
    </view>
    
    <!-- 未登录状态 -->
    <view class="user-info not-login" v-else @click="goToLogin">
      <view class="avatar-box">
        <image class="avatar" src="/static/default-avatar.png" mode="aspectFill"></image>
      </view>
      <view class="user-detail">
        <view class="nickname">点击登录</view>
      </view>
    </view>
    
    <!-- 功能菜单 -->
    <view class="menu-list">
      <view class="menu-group">
        <view class="menu-item" @click="handleMenuClick('myPublish')">
          <text class="iconfont icon-publish"></text>
          <text class="menu-text">我的发布</text>
          <text class="iconfont icon-right"></text>
        </view>
        <view class="menu-item" @click="handleMenuClick('myOrders')">
          <text class="iconfont icon-order"></text>
          <text class="menu-text">我的订单</text>
          <text class="iconfont icon-right"></text>
        </view>
      </view>
      
      <view class="menu-group">
        <view class="menu-item" @click="handleMenuClick('news')">
          <text class="iconfont icon-news"></text>
          <text class="menu-text">公告资讯</text>
          <text class="iconfont icon-right"></text>
        </view>
        <view class="menu-item" @click="handleMenuClick('authenticate')" v-if="isLogin">
          <text class="iconfont icon-auth"></text>
          <text class="menu-text">我的认证</text>
          <text class="iconfont icon-right"></text>
        </view>
      </view>
      
      <view class="menu-group" v-if="isLogin">
        <view class="menu-item" @click="handleMenuClick('logout')">
          <text class="iconfont icon-logout"></text>
          <text class="menu-text">退出登录</text>
          <text class="iconfont icon-right"></text>
        </view>
      </view>
      
      <view class="menu-group" v-else>
      <!--  <view class="menu-item" @click="handleMenuClick('register')">
          <text class="iconfont icon-register"></text>
          <text class="menu-text">注册</text>
          <text class="iconfont icon-right"></text>
        </view> -->
        <view class="menu-item" @click="handleMenuClick('login')">
          <text class="iconfont icon-login"></text>
          <text class="menu-text">登录</text>
          <text class="iconfont icon-right"></text>
        </view>
      </view>
    </view>
  </view>
</template>

<script>
import { getCurrentUser } from '@/api/user.js'

export default {
  data() {
    return {
      isLogin: false,
      userInfo: {}
    }
  },
  onShow() {
    // 每次显示页面时检查登录状态
    this.checkLoginStatus()
  },
  onLoad() {
    // 页面加载时立即检查登录状态,如果未登录则跳转到登录页
    const token = uni.getStorageSync('token')
    if (!token) {
      uni.navigateTo({
        url: '/pages/login/login'
      })
    }
  },
  methods: {
    // 检查登录状态
    checkLoginStatus() {
      const token = uni.getStorageSync('token')
      if (token) {
        this.isLogin = true
        this.getUserInfo()
      } else {
        this.isLogin = false
        this.userInfo = {}
      }
    },
    
    // 获取用户信息
    getUserInfo() {
      getCurrentUser().then(res => {
        if (res.code === 1000) {
          this.userInfo = res.data
        }
      }).catch(() => {
        this.isLogin = false
        this.userInfo = {}
      })
    },
    
    // 跳转到登录页
    goToLogin() {
      uni.navigateTo({
        url: '/pages/login/login'
      })
    },
    
    // 菜单点击处理
    handleMenuClick(type) {
      switch(type) {
        case 'myPublish':
          // 检查登录状态
          if (!this.isLogin) {
            uni.showToast({
              title: '请先登录',
              icon: 'none'
            })
            this.goToLogin()
            return
          }
          // 跳转到我的发布页面
          uni.navigateTo({
            url: '/pages/my/published'
          })
          break
        case 'myOrders':
          // 检查登录状态
          if (!this.isLogin) {
            uni.showToast({
              title: '请先登录',
              icon: 'none'
            })
            this.goToLogin()
            return
          }
          // 跳转到我的订单页面
          uni.navigateTo({
            url: '/pages/my/orders'
          })
          break
        case 'news':
          uni.navigateTo({
            url: '/pages/news/list'
          })
          break
        case 'authenticate':
          // 跳转到认证页面
          uni.navigateTo({
            url: '/pages/my/authenticate'
          })
          break
        case 'register':
          uni.showToast({
            title: '注册功能开发中',
            icon: 'none'
          })
          break
        case 'login':
          this.goToLogin()
          break
        case 'logout':
          this.handleLogout()
          break
      }
    },
    
    // 退出登录
    handleLogout() {
      uni.showModal({
        title: '提示',
        content: '确定要退出登录吗?',
        success: (res) => {
          if (res.confirm) {
            uni.removeStorageSync('token')
            this.isLogin = false
            this.userInfo = {}
            uni.showToast({
              title: '已退出登录',
              icon: 'success'
            })
          }
        }
      })
    }
  }
}
</script>

<style lang="scss">
.my-container {
  min-height: 100vh;
  background-color: #f5f5f5;
  
  .user-info {
    display: flex;
    align-items: center;
    padding: 30rpx;
    background-color: #ffffff;
    
    &.not-login {
      opacity: 0.8;
    }
    
    .avatar-box {
      width: 120rpx;
      height: 120rpx;
      border-radius: 50%;
      overflow: hidden;
      margin-right: 30rpx;
      
      .avatar {
        width: 100%;
        height: 100%;
        background-color: #eeeeee;
      }
    }
    
    .user-detail {
      flex: 1;
      
      .nickname {
        font-size: 32rpx;
        font-weight: bold;
        margin-bottom: 10rpx;
      }
      
      .auth-status {
        .auth-tag {
          display: inline-block;
          font-size: 24rpx;
          padding: 4rpx 12rpx;
          border-radius: 20rpx;
          
          &.auth-yes {
            background-color: #e6f7ff;
            color: #1890ff;
          }
          
          &.auth-no {
            background-color: #fff7e6;
            color: #fa8c16;
          }
        }
      }
    }
  }
  
  .menu-list {
    margin-top: 20rpx;
    
    .menu-group {
      background-color: #ffffff;
      margin-bottom: 20rpx;
      
      .menu-item {
        display: flex;
        align-items: center;
        padding: 30rpx;
        border-bottom: 1rpx solid #f0f0f0;
        
        &:last-child {
          border-bottom: none;
        }
        
        .iconfont {
          font-size: 40rpx;
          color: #666;
          margin-right: 20rpx;
          
          &.icon-right {
            margin-right: 0;
            margin-left: auto;
            font-size: 32rpx;
            color: #999;
          }
        }
        
        .menu-text {
          font-size: 28rpx;
          color: #333;
        }
      }
    }
  }
}
</style>

如果你刚开始学习Java,并且想通过实践提升自己,可以参考这篇文章,尝试模仿并独立完成一个类似的项目。

把项目部署了一个预览地址,方便大家参考。
https://test.wwwoop.com/?s=/er-shou-ping-tai-web&no=Second-hand-TP003&rand=0.6679227765871157

相关推荐
旷野说1 分钟前
为什么 MyBatis 原生二级缓存“难以修复”?
java·java-ee·mybatis
8***23554 分钟前
【wiki知识库】07.用户管理后端SpringBoot部分
java
bcbnb6 分钟前
iOS 性能测试的工程化方法,构建从底层诊断到真机监控的多工具测试体系
后端
开心就好20259 分钟前
iOS 上架 TestFlight 的真实流程复盘 从构建、上传到审核的团队协作方式
后端
小周在成长17 分钟前
Java 泛型支持的类型
后端
aiopencode17 分钟前
Charles 抓不到包怎么办?HTTPS 抓包失败、TCP 数据流异常与底层补抓方案全解析
后端
阿蔹20 分钟前
JavaWeb-Selenium 配置以及Selenim classnotfound问题解决
java·软件测试·python·selenium·测试工具·自动化
稚辉君.MCA_P8_Java22 分钟前
Gemini永久会员 C++返回最长有效子串长度
开发语言·数据结构·c++·后端·算法
Penge66641 分钟前
Redis-bgsave浅析
redis·后端
阿白的白日梦1 小时前
Windows下c/c++编译器MinGW-w64下载和安装
c语言·后端