心链11-----队伍相关业务代码(前后端)(事务注解讲解)

心链 — 伙伴匹配系统

用户可以退出队伍

请求参数:队伍 id

  1. 校验请求参数
  2. 校验队伍是否存在
  3. 校验我是否已加入队伍
  4. 如果队伍
  5. 只剩一人,队伍解散
  6. 还有其他人
    1. 如果是队长退出队伍,权限转移给第二早加入的用户 —— 先来后到 只用取 id 最小的 2 条数据
    2. 非队长,自己退出队伍

新建退出请求体

	@Data
    public class TeamQuitRequest implements Serializable {
        private static final long serialVersionUID = -2038884913144640407L;
        /**
        *  id
        */
        private Long teamId;
    }

新建quit请求接口

@PostMapping("/quit")
    public BaseResponse<Boolean> quitTeam(@RequestBody TeamQuitRequest teamQuitRequest,HttpServletRequest request){
        if (teamQuitRequest == null){
        throw new BusinessException(ErrorCode.PARAMS_ERROR);
    }
    User loginUser = userService.getLoginUser(request);
    boolean result = teamService.quitTeam(teamQuitRequest, loginUser);
    return ResultUtils.success(result);
}

在TeamService是写入quitTeam方法

/**
* 退出队伍
* @param teamQuitRequest
* @param loginUser
* @return
*/
boolean quitTeam(TeamQuitRequest teamQuitRequest, User loginUser);

在TeamServiceImpl里实现quitTeam方法

	@Override
    @Transactional(rollbackFor = Exception.class)
    public boolean quitTeam(TeamQuitRequest teamQuitRequest, User loginUser) {
    if (teamQuitRequest == null) {
        throw new BusinessException(ErrorCode.PARAMS_ERROR);
    }
    Long teamId = teamQuitRequest.getTeamId();
    if (teamId == null || teamId <= 0) {
        throw new BusinessException(ErrorCode.PARAMS_ERROR);
    }
    Team team = this.getById(teamId);
    if (team == null) {
        throw new BusinessException(ErrorCode.NULL_ERROR, "队伍不存在");
    }
    long userId = loginUser.getId();
    UserTeam queryUserTeam = new UserTeam();
    queryUserTeam.setTeamId(teamId);
    queryUserTeam.setUserId(userId);
    QueryWrapper<UserTeam> queryWrapper = new QueryWrapper<>(queryUserTeam);
    long count = userTeamService.count(queryWrapper);
    if (count == 0) {
        throw new BusinessException(ErrorCode.PARAMS_ERROR, "未加入队伍");
    }
    long teamHasJoinNum = this.countTeamUserByTeamId(teamId);
    //队伍只剩下一个人,解散
    if (teamHasJoinNum == 1) {
        //删除队伍
        this.removeById(teamId);
    } else {
        //队伍至少还剩下两人
        //是队长
        if (team.getUserId() == userId) {
            //把队伍转移给最早加入的用户
            //1.查询已加入队伍的所有用户和加入时间
            QueryWrapper<UserTeam> userTeamQueryWrapper = new QueryWrapper<>();
            userTeamQueryWrapper.eq("teamId", teamId);
            userTeamQueryWrapper.last("order by id asc limit 2");
            List<UserTeam> userTeamList = userTeamService.list(userTeamQueryWrapper);
            if (CollectionUtils.isEmpty(userTeamList) || userTeamList.size() <= 1) {
                throw new BusinessException(ErrorCode.SYSTEM_ERROR);
            }
            UserTeam nextUserTeam = userTeamList.get(1);
            Long nextTeamLeaderId = nextUserTeam.getUserId();
            //更新当前队伍的队长
            Team updateTeam = new Team();
            updateTeam.setId(teamId);
            updateTeam.setUserId(nextTeamLeaderId);
            boolean result = this.updateById(updateTeam);
            if (!result) {
                throw new BusinessException(ErrorCode.SYSTEM_ERROR, "更新队伍队长失败");
            }
        }
    }
    //移除关系
    return userTeamService.remove(queryWrapper);
}

这里我们由于多次需要获得队伍当前人数,所以封装了countTeamUserByTeamId方法

/**
* 获取某队伍当前人数
*
* @param teamId
* @return
*/
private long countTeamUserByTeamId(long teamId) {
    QueryWrapper<UserTeam> userTeamQueryWrapper = new QueryWrapper<>();
    userTeamQueryWrapper.eq("teamId", teamId);
    return userTeamService.count(userTeamQueryWrapper);
}

image.png

测试
队伍1中有5和32两个user
image.png
让1退出
image.png
image.png
队伍成功顺位给第二位32
image.png
让32再退出
image.png
队伍直接解散
image.png

队长可以解散队伍

请求参数:队伍 id
业务流程:

  1. 校验请求参数
  2. 校验队伍是否存在
  3. 校验你是不是队伍的队长
  4. 移除所有加入队伍的关联信息
  5. 删除队伍

在TeamService里编写删除队伍方法并在TeamServiceImpl里实现
修改delete接口

@PostMapping("/delete")
    public BaseResponse<Boolean> deleteTeam(@RequestBody long id,HttpServletRequest request) {
    if (id <= 0) {
    	throw new BusinessException(ErrorCode.PARAMS_ERROR);
	}
    User loginUser = userService.getLoginUser(request);
    boolean result = teamService.deleteTeam(id,loginUser);
    if (!result) {
        throw new BusinessException(ErrorCode.SYSTEM_ERROR, "删除失败");
    }
    return ResultUtils.success(true);
}

在TeamService里面写入deleteTeam方法

/**
* 删除队伍
* @param id
* @param loginUser
* @return
*/
boolean deleteTeam(long id, User loginUser);

在TeamServiceImpl里实现deleteTeam方法
跟上面一样,我们需要根据id获取队伍信息,这个代码我们重复的写,所以提取出来

/**
* 根据 id 获取队伍信息
*
* @param teamId
* @return
*/
private Team getTeamById(Long teamId) {
    if (teamId == null || teamId <= 0) {
    throw new BusinessException(ErrorCode.PARAMS_ERROR);
}
Team team = this.getById(teamId);
if (team == null) {
    throw new BusinessException(ErrorCode.NULL_ERROR, "队伍不存在");
}
return team;
}

在Error_Code里添加一个禁止操作
image.png

@Override
@Transactional(rollbackFor = Exception.class)
public boolean deleteTeam(long id, User loginUser) {
    // 校验队伍是否存在
    Team team = getTeamById(id);
    long teamId = team.getId();
    // 校验你是不是队伍的队长
    if (!team.getUserId().equals(loginUser.getId())){
        throw new BusinessException(ErrorCode.NO_AUTH,"无访问权限");
    }
    // 移除所有加入队伍的关联信息
    QueryWrapper<UserTeam> userTeamQueryWrapper = new QueryWrapper<>();
    userTeamQueryWrapper.eq("teamId", teamId);
    boolean result = userTeamService.remove(userTeamQueryWrapper);
    if (!result){
        throw new BusinessException(ErrorCode.SYSTEM_ERROR,"删除队伍关联信息失败");
    }
    // 删除队伍
    return this.removeById(teamId);
}

image.png
成功解散
image.png

:::danger

事务注解

@Transactional(rollbackFor = Exception.class)
要么数据操作都成功,要么都失败
:::

TODO

获取当前用户已加入的队伍
获取当前用户创建的队伍

复用 listTeam 方法,只新增查询条件,不做修改(开闭原则)

前端

加入队伍页面

新建一个TeamAddPage,并在路由里添加这个页面
image.png
在TeamPage里写一个按钮跳转到TeamAddPage
image.png

设计TeamAddPage页面,主要是在vant组件库里选择合适的组件

(1).队伍名和描述名

我们可以发现队伍名和描述名类似于用户登录页面的表单组件,所以拿来即用(修改下参数)
这个主要是运用了表单,单元格,输入框这三个组件,其中描述使用了高度自适应
image.png

参数我们可以从后台获得(knife4j接口文档)
image.png

(2).过期时间

我们选择vant里的DatePicker 日期选择和from种的时间选择器

在这里插入图片描述

这里的min-date 我们不能直接new Date(),因为这会导致页面一直渲染,从而页面加载不出来,我能得在建一个常量min-date,同时这个日期默认不显示,我们要在JS里展示日期选择器,确当按钮函数

在这里插入图片描述

踩坑: 由于这里官方文档修改了,在这里我们前后端都要加上一个时间格式化

前端时间格式化:

下载一个moment格式化工具 npm i moent

在这里插入图片描述

后端时间格式化

在这里插入图片描述

在expireTime属性加上一个格式化注解,并给定格式

在这里插入图片描述

(3).最大人数

这里我们选择Stepper不进器里的限制输入范围
image.png

(4).队伍状态(当只有选择加密队伍时,才会跳出密码框)

这里我们选择表单类型里的单选框,和field输入框。
注意一定要在判断状态时,把类型转为Number,因为通过打印可得,状态是字符串类型的。
image.png

(5).提交按钮

image.png

native-type="submit"属性, 点击自动获取van-field name中的值组成的对象。
关键是提交所传的的状态也要转换成Number,同时创建成功后跳转到队伍页面

image.png

AddTeamPage页面完整代码如下:

<!--
User:Shier
CreateTime:19:54
-->
<template>
  <div id="teamAddPage">
    <van-form @submit="onSubmit">
      <van-cell-group inset>
        <van-field
            v-model="addTeamData.name"
            name="name"
            label="队伍名称"
            placeholder="请输入队伍名称"
            :rules="[{ required: true, message: '请输入队伍名称' }]"
        />
        <van-field
            v-model="addTeamData.description"
            rows="4"
            autosize
            label="队伍描述"
            type="textarea"
            placeholder="请输入队伍描述"
        />
        <!--过期时间-->
        <van-field
            is-link
            readonly
            name="datePicker"
            label="时间选择"
            :placeholder="addTeamData.expireTime ?? '点击选择关闭队伍加入的时间'"
            @click="showPicker = true"
        />
        <van-popup v-model:show="showPicker" position="bottom">
          <van-date-picker
              @confirm="onConfirm"
              @cancel="showPicker = false"
              type="datetime"
              title="请选择关闭队伍加入的时间"
              :min-date="minDate"/>
        </van-popup>

        <van-field name="stepper" label="最大人数">
          <template #input>
            <van-stepper v-model="addTeamData.maxNum" max="10" min="3"/>
          </template>
        </van-field>

        <van-field name="radio" label="队伍状态">
          <template #input>
            <van-radio-group v-model="addTeamData.status" direction="horizontal">
              <van-radio name="0">公开</van-radio>
              <van-radio name="1">私有</van-radio>
              <van-radio name="2">加密</van-radio>
            </van-radio-group>
          </template>
        </van-field>

        <van-field
            v-if="Number(addTeamData.status) === 2"
            v-model="addTeamData.password"
            type="password"
            name="password"
            label="密码"
            placeholder="请输入队伍密码"
            :rules="[{ required: true, message: '请填写密码' }]"
        />
      </van-cell-group>
      <div style="margin: 16px;">
        <van-button round block type="primary" native-type="submit">
          提交
        </van-button>
      </div>
    </van-form>
  </div>
</template>

<script setup lang="ts">

  import {useRouter} from "vue-router";
  import {ref} from "vue";
  import myAxios from "../plugins/myAxios";
  import moment from 'moment';
  import {showFailToast, showSuccessToast} from "vant/lib/vant.es";

  const router = useRouter();


  // 日期展示器
  const showPicker = ref(false);
  // 当前时间
  const minDate = new Date();

  const onConfirm = ({selectedValues}) => {
    addTeamData.value.expireTime = selectedValues.join('-');
    showPicker.value = false;
  };

  const initFormData = {
    "name": "",
    "description": "",
    "expireTime": null,
    "maxNum": 5,
    "password": "",
    "status": 0,
  }

  // 需要用户填写的表单数据 对象扩展
  const addTeamData = ref({...initFormData})

  // 提交
  const onSubmit = async () => {
    const postData = {
      ...addTeamData.value,
      status: Number(addTeamData.value.status),
      expireTime: moment(addTeamData.value.expireTime).format("YYYY-MM-DD HH:mm:ss")
    }
    const res = await myAxios.post("/team/add", postData);
    if (res?.code === 0 && res.data) {
      showSuccessToast('添加成功');
      router.push({
        path: '/team',
        replace: true,
      });
    } else {
      showFailToast('添加失败');
    }
  }
</script>

<style scoped>
  #teamPage {

  }
</style>

image.png

队伍表单

我们首先要定义队伍类型(team.d.ts)

import {UserType} from "./user";

/**
 * 队伍类别
 */
export type TeamType = {
    id: number;
    name: string;
    description: string;
    expireTime?: Date;//表示可有可无
    maxNum: number;
    password?: string,
    // todo 定义枚举值类型,更规范
    status: number;
    createTime: Date;
    updateTime: Date;
    createUser?: UserType;
    hasJoinNum?: number;
};

创建一个队伍卡片列表组件(类似于用户卡片列表)
(1).复制用户卡片列表,将userlist改为teamlist,UserCardList改为TeamCardList,UserType改为TeamType

<template>
  <van-card
      v-for="user in props.teamList"
      :desc="user.profile"
      :title="`${user.username} (${user.planetCode})`"
      :thumb="user.avatarUrl"
  >
    <template #tags>
      <van-tag plain type="danger" v-for="tag in user.tags" style="margin-right: 8px; margin-top: 8px" >
        {{ tag }}
      </van-tag>
    </template>
    <template #footer>
      <van-button size="mini">联系我</van-button>
    </template>
  </van-card>
</template>

<script setup lang="ts">

import {TeamType} from "../models/team";

interface TeamCardListProps{
  teamList: TeamType[];
}

const props= withDefaults(defineProps<TeamCardListProps>(),{
  //@ts-ignore
  teamList: [] as TeamType[]
});

</script>

<style scoped>

</style>

然后我们将此组件挂载在TeamPage页面
image.png
image.png
注意:引入team-card-list时,编译器可能不会帮你把引入的类型自动带上,需自己添加
image.png
将team-card-list里的原来的用户参数换成队伍的,测试一下

image.png
刷新页面,成功加载出组件(就是很丑,展示不齐全)
image.png
现在我们要完善teamcardlist组件

添加队伍状态,最大人数等以及实现加入队伍功能
我们下方要涉及到队伍的状态,我们先创建队伍状态常量 team.ts
image.png

<template>
  <div>
    <van-card
        v-for="team in props.teamList"
        :thumb="mouse"
        :desc="team.description"
        :title="`${team.name}`"
    >
      <template #tags>
        <van-tag plain type="danger" style="margin-right: 8px; margin-top: 8px">
          {{
            teamStatusEnum[team.status]
          }}
        </van-tag>
      </template>
      <template #bottom>
        <div>
          {{ '最大人数: ' + team.maxNum }}
        </div>
        <div v-if="team.expireTime">
          {{ '过期时间: ' + team.expireTime }}
        </div>
        <div>
          {{ '创建时间: ' + team.createTime }}
        </div>
      </template>
      <template #footer>
        <van-button size="small" type="primary"  plain @click="doJoinTeam(team.id)">加入队伍</van-button>
      </template>
    </van-card>
  </div>

</template>

<script setup lang="ts">
import {TeamType} from "../models/team";
import {teamStatusEnum} from "../constants/team";
import mouse from '../assets/mouse.jpg';
import myAxios from "../plugins/myAxios";
import {Toast} from "vant";

import {useRouter} from "vue-router";

interface TeamCardListProps {
  teamList: TeamType[];
}

const props = withDefaults(defineProps<TeamCardListProps>(), {
  // @ts-ignore
  teamList: [] as TeamType[],
});

const router = useRouter();

/**
 * 加入队伍
 */
const doJoinTeam = async (id:number) => {
  const res = await myAxios.post('/team/join', {
    teamId: id,
  });
  if (res?.code === 0) {
    Toast.success('加入成功');
  } else {
    Toast.fail('加入失败' + (res.description ? `${res.description}` : ''));
  }
}

</script>

<style scoped>
#teamCardList :deep(.van-image__img) {
  height: 128px;
  object-fit: unset;
}
</style>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值