最近我用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

相关推荐
YuanlongWang4 小时前
C#基础——GC(垃圾回收)的工作流程与优化策略
java·jvm·c#
爱吃生蚝的于勒4 小时前
【Linux】深入理解进程(一)
java·linux·运维·服务器·数据结构·c++·蓝桥杯
毅炼4 小时前
常见排序算法
java·算法·排序算法
自由会客室4 小时前
在 Ubuntu24.04 上安装 JDK 21(Java 21)
java·开发语言
调试人生的显微镜4 小时前
前端一般用什么开发工具?一文看懂从入门到专业的完整工具链
后端
喜欢读源码的小白4 小时前
SpringBoot的启动流程原理——小白的魔法引擎探秘
java·开发语言·spring boot·springboot启动原理
白露与泡影4 小时前
BAT 大厂 java高频面试题汇总:JVM+Spring+ 分布式 +tomcat+MyBatis
java·jvm·spring
Han.miracle4 小时前
数据结构——排序的学习(一)
java·数据结构·学习·算法·排序算法
摇滚侠4 小时前
Spring Boot 3零基础教程,WEB 开发 通过配置类代码方式修改静态资源配置 笔记32
java·spring boot·笔记