第8章 Node框架实战篇 - 文件上传与管理

频道创建及用户信息修改操作

用户 Schema 新增字段

model\userModel.js

javascript 复制代码
const mongoose = require('mongoose');
const md5 = require('../util/md5');
const baseSchema = require('./baseModel');

const userSchema = new mongoose.Schema({
  cover: {
    type: String,
    default: null,
  },
  channeldes: {
    type: String,
    default: null,
  },
  ...baseSchema,
});

module.exports = userSchema;

router\user.js

新增更新用户路由

javascript 复制代码
const validator = require('../middleware/validator/userValidator');
// 更新用户信息
router.put('/', verifyToken, validator.update, userConroller.update);

middleware\validator\userValidator.js

添加更新用户信息参数校验

javascript 复制代码
// 更新用户信息校验规则
module.exports.update = validate([
  body('username')
    .notEmpty()
    .withMessage('用户名不能为空')
    .bail()
    .isLength({ min: 3 })
    .withMessage('用户名长度最小为3')
    .bail()
    .custom((value) => {
      return User.findOne({ username: value }).then((user) => {
        if (user) {
          return Promise.reject('此用户已注册');
        }
      });
    })
    .bail(),
  body('email')
    .notEmpty()
    .withMessage('邮箱不能为空')
    .bail()
    .isEmail()
    .withMessage('邮箱格式不正确')
    .bail()
    .custom((value) => {
      return User.findOne({ email: value }).then((user) => {
        if (user) {
          return Promise.reject('邮箱已注册');
        }
      });
    })
    .bail(),
  body('phone')
    .notEmpty()
    .withMessage('手机不能为空')
    .bail()
    .custom((value) => {
      return User.findOne({ phone: value }).then((user) => {
        if (user) {
          return Promise.reject('手机号已注册');
        }
      });
    })
    .bail(),
]);

controller\user.js

处理更新用户信息业务逻辑

javascript 复制代码
// 更新用户信息
const update = async (req, res) => {
  try {
    // 更新用户信息
    const id = req.user.user;
    const dbBack = await User.findByIdAndUpdate(id, req.body, {
      new: true,
    });
    res.status(200).json(dbBack);
  } catch (error) {
    res.status(400).json(error.message);
  }
};
文件上传操作

安装 multer 文件上传库

javascript 复制代码
pnpm i multer@1.4.4 -S

router\user.js

在上传用户头像路由添加上传图片中间件

javascript 复制代码
const multer = require('multer');
const upload = multer({ dest: 'public/' });

// 上传头像
router.post(
  '/headimg',
  verifyToken,
  upload.single('headimg'),
  userConroller.upload
);

controller\user.js

处理上传图片后的处理逻辑

javascript 复制代码
// 上传用户头像
const upload = async (req, res) => {
  try {
    const fileArr = req.file.originalname.split('.');
    const fileType = fileArr[fileArr.length - 1];
    const oldPath = `./public/${req.file.filename}`;
    const newPath = `./public/${req.file.filename}.${fileType}`;
    await rename(oldPath, newPath);
    res.status(200).json(newPath);
  } catch (error) {
    res.status(400).json(error.message);
  }
};

访问用户头像

VoD视频点播服务

安装包

复制代码
pnpm i @alicloud/pop-core -S

controller\vodController.js

使用安装包提供的服务获取上传凭证信息

javascript 复制代码
var RPCClient = require('@alicloud/pop-core').RPCClient;

// 初始化点播客户端
function initVodClient(accessKeyId, accessKeySecret) {
  const regionId = 'cn-shanghai'; // 点播服务接入区域
  var client = new RPCClient({
    accessKeyId: accessKeyId,
    accessKeySecret: accessKeySecret,
    endpoint: 'vod.' + regionId + '.aliyuncs.com',
    apiVersion: '2017-03-21',
  });
  return client;
}

// 获取点播视频上传地址和凭证
exports.getVod = async (req, res) => {
  var client = initVodClient('LTAI5t3Yxxxxxx', 'xxxxxxxxxxxxxxxxxxxxxx');

  const vodBack = await client.request('CreateUploadVideo', {
    Title: 'xxxxxxxxxxxxxx',
    FileName: 'xxxxxxxxxxxxxx.mp4',
  });
  res.status(200).json({ vod: vodBack });
};

router\video.js

定义获取视频上传凭证路由

javascript 复制代码
// 获取点播视频上传地址和凭证
router.get('getVod', vodController.getVod);
客户端上传VoD

https://blog.csdn.net/u014494148/article/details/132144301

视频信息入库操作

model\videoModel.js

定义视频 video Schema

javascript 复制代码
const mongoose = require('mongoose');
const baseSchema = require('./baseModel');

const videoSchema = new mongoose.Schema({
  title: {
    type: String,
    required: true,
  },
  descrption: {
    type: String,
    required: true,
  },
  vodvideoId: {
    type: String,
    required: true,
  },
  user: {
    type: mongoose.Schema.Types.ObjectId,
    required: true,
    ref: 'User',
  },
  cover: {
    type: String,
    required: true,
  },
  ...baseSchema,
});

module.exports = videoSchema;

model\index.js

注册 videoModel

javascript 复制代码
module.exports = {
  User: mongoose.model('User', require('./userModel')),
  Video: mongoose.model('Video', require('./videoModel')),
};

middleware\validator\videoValidator.js

路由参数校验

javascript 复制代码
const { body } = require('express-validator');
const validate = require('./errorBack');
// const { User } = require('../../model');
// 上传视频验证规则
module.exports.videoValidator = validate([
  body('title')
    .notEmpty()
    .withMessage('视频名称不能为空')
    .bail()
    .isLength({ max: 20 })
    .withMessage('视频名长度不能超过20')
    .bail(),
  body('vodvideoId').notEmpty().withMessage('Vod不能为空').bail(),
]);

controller\videoController.js

处理视频上传业务逻辑

javascript 复制代码
const { Video } = require('../model');

// 上传视频
exports.createVideo = async (req, res) => {
  var body = req.body;
  body.user = req.user._id;

  const videoModel = new Video(body);
  try {
    var dbBack = await videoModel.save();
    res.status(201).send({
      code: 201,
      message: '视频上传成功',
      data: dbBack,
    });
  } catch (error) {
    res.status(500).send({
      code: 500,
      message: '视频上传失败',
      data: error,
    });
  }
};

router\video.js

定义上传视频路由

javascript 复制代码
const express = require('express');
const router = express.Router();

const { verifyToken } = require('../util/jwt');

const vodController = require('../controller/vodController');
const videoController = require('../controller/videoController');
const { videoValidator } = require('../middleware/validator/videoValidator');

// 获取点播视频上传地址和凭证
router.get('getVod', vodController.getVod);
router.get(
  'createvideo',
  verifyToken,
  videoValidator,
  videoController.createVideo
);
module.exports = router;
视频列表及分页展示

router\video.js

定义视频列表路由

javascript 复制代码
router.get('/videolist', verifyToken, videoController.videolist);

controller\videoController.js

处理视频分页列表逻辑

javascript 复制代码
// 视频分页列表
exports.videolist = async (req, res) => {
  var { page = 1, size = 10 } = req.query;
  page = parseInt(page);
  size = parseInt(size);
  try {
    var dbBack = await Video.find()
      .skip((page - 1) * size)
      .limit(size)
      .sort({
        createdAt: -1,
      })
      .populate('user');
    // 视频总条数
    var total = await Video.countDocuments();
    res.status(200).send({
      code: 200,
      message: '视频列表获取成功',
      total: total,
      data: dbBack,
    });
  } catch (error) {
    res.status(500).send({
      code: 500,
      message: '视频列表获取失败',
      data: error,
    });
  }
};
获取视频详情

util\jwt.js

重新修改验证,使其能够自定义是否验证 token

javascript 复制代码
/**
 * @description 验证 token 中间件
 * @param {*} required 是否需要验证 token, 默认不验证
 * @returns
 */
module.exports.verifyToken = function (required = false) {
  return async (req, res, next) => {
    //从 header 读取 token, 并解析 token
    const authorization = req.headers.authorization;
    const token = authorization ? authorization.split('Bearer ')[1] : null;
    if (token) {
      try {
        const user = await verifyJwt(token, uuid);
        // 将用户信息存储到 req.user 中
        req.user = user;
        next();
      } catch (error) {
        res.status(401).send('token 验证失败');
      }
    } else if (required) {
      res.status(401).send('缺少 token');
    } else {
      next();
    }
  };
};

router\video.js

定义获取视频详情路由

javascript 复制代码
// 视频详情
router.get('/video/:videoId', verifyToken(false), videoController.videodetail);

controller\videoController.js

处理获取视频详情逻辑

javascript 复制代码
// 获取视频详情
exports.videodetail = async (req, res) => {
  var { videoId } = req.params;
  try {
    var dbBack = await Video.findById(videoId).populate('user');
    if (dbBack) {
      res.status(200).send({
        code: 200,
        message: '视频详情获取成功',
        data: dbBack,
      });
    } else {
      res.status(404).send({
        code: 404,
        message: '视频不存在',
      });
    }
  } catch (error) {
    res.status(500).send({
      code: 500,
      message: '视频详情获取失败',
      data: error,
    });
  }
};
频道订阅与取消订阅
频道订阅

router\user.js

定义频道关注路由

javascript 复制代码
// 关注用户
router.get('/follow/:userId', verifyToken(), userConroller.follow);

controller\user.js

实现频道订阅逻辑

javascript 复制代码
// 关注用户
const follow = async (req, res) => {
  try {
    const userId = req.user.user._id;
    const followId = req.params.userId;
    // 不能关注自己
    if (userId === followId) {
      return res.status(400).json('不能关注自己');
    }
    // 是否已关注
    const existingFollow = await Follow.findOne({
      user: userId,
      channle: followId,
    });
    if (existingFollow) {
      return res.status(400).json('已关注该用户');
    } else {
      // 创建关注记录
      const followModel = new Follow({
        user: userId,
        channle: followId,
      });
      await followModel.save();
      // 更新被关注用户的粉丝数
      await User.findByIdAndUpdate(followId, { $inc: { followCount: 1 } });
      res.status(201).json('关注成功');
    }
  } catch (error) {
    res.status(400).json(error.message);
  }
};

model\followModel.js

定义频道关注关系 Model

javascript 复制代码
const mongoose = require('mongoose');
const baseSchema = require('./baseModel');

const followSchema = new mongoose.Schema({
  user: {
    type: mongoose.Schema.Types.ObjectId,
    required: true,
    ref: 'User',
  },
  channle: {
    type: mongoose.Schema.Types.ObjectId,
    required: true,
    ref: 'User',
  },
  ...baseSchema,
});

module.exports = followSchema;

model\userModel.js

用户 model 新增粉丝数字段

javascript 复制代码
 // 粉丝数
  followCount: {
    type: Number,
    default: 0,
  },
取消订阅

router\user.js

定义取消订阅路由

javascript 复制代码
// 取消关注用户
router.get('/unfollow/:userId', verifyToken(), userConroller.unfollow);

controller\user.js

处理取消订阅逻辑

javascript 复制代码
// 取消关注用户
const unfollow = async (req, res) => {
  try {
    const userId = req.user.user._id;
    const followId = req.params.userId;
    // 不能关注自己
    if (userId === followId) {
      return res.status(400).json('不能取消关注自己');
    }
    // 是否已关注
    const existingFollow = await Follow.findOne({
      user: userId,
      channle: followId,
    });
    if (existingFollow) {
      // 删除关注记录
      await Follow.deleteOne({ _id: existingFollow._id });
      // 更新被关注用户的粉丝数
      await User.findByIdAndUpdate(followId, { $inc: { followCount: -1 } });
      res.status(200).json('取消关注成功');
    } else {
      res.status(400).json('未关注该用户');
    }
  } catch (error) {
    res.status(400).json(error.message);
  }
};
获取频道信息

router\user.js

定义获取用户详情路由

javascript 复制代码
// 获取用户信息
router.get('/:userId', verifyToken(false), userConroller.getUserInfo);

controller\user.js

实现获取用户详情逻辑

javascript 复制代码
// 获取用户信息
const getUserInfo = async (req, res) => {
  try {
    // 是否关注此用户
    var isFlow = false;
    // 已经登录了
    if (req.user) {
      // 是否关注此用户
      const existingFollow = await Follow.findOne({
        user: req.user.user._id,
        channle: req.params.userId,
      });
      isFlow = existingFollow ? true : false;
    }
    const user = await User.findById(req.params.userId);
    user.isFlow = isFlow;
    res.status(200).json({
      ...lodash.pick(user, ['_id', 'username', 'image', 'followCount']),
      isFlow,
    });
  } catch (error) {
    res.status(400).json(error.message);
  }
};
关注列表与粉丝列表
关注列表

router\user.js

定义关注列表路由

javascript 复制代码
// 关注列表
router.get('/followList/:userId', userConroller.followList);

controller\user.js

实现关注列表逻辑

javascript 复制代码
// 关注列表
const followList = async (req, res) => {
  try {
    const userId = req.params.userId;
    // 获取关注列表
    const follows = await Follow.find({ user: userId }).populate('channle');
    // 整理返回数据
    const followList = follows.map((follow) => {
      return lodash.pick(follow.channle, [
        '_id',
        'username',
        'image',
        'followCount',
      ]);
    });
    res.status(200).json(followList);
  } catch (error) {
    res.status(400).json(error.message);
  }
};
粉丝列表

router\user.js

定义获取粉丝列表路由

javascript 复制代码
// 获取粉丝列表
router.get('/fansList', verifyToken(), userConroller.fansList);

controller\user.js

实现获取粉丝列表逻辑

javascript 复制代码
// 粉丝列表
const fansList = async (req, res) => {
  try {
    const userId = req.user.user._id;
    // 获取粉丝列表
    const fans = await Follow.find({ channle: userId }).populate('user');
    // 整理返回数据
    const fansList = fans.map((fan) => {
      return lodash.pick(fan.user, ['_id', 'username', 'image', 'followCount']);
    });
    res.status(200).json(fansList);
  } catch (error) {
    res.status(400).json(error.message);
  }
};
添加视频评论

router\video.js

添加视频评论路由

javascript 复制代码
// 视频评论
router.post('/comment/:videoId', verifyToken(), videoController.comment);

model\videocommentModel.js

定义评论 Model

javascript 复制代码
const mongoose = require('mongoose');
const baseSchema = require('./baseModel');

const videocommentSchema = new mongoose.Schema({
  content: {
    type: String,
    required: true,
  },
  video: {
    type: mongoose.Schema.Types.ObjectId,
    required: true,
    ref: 'Video',
  },
  user: {
    type: mongoose.Schema.Types.ObjectId,
    required: true,
    ref: 'User',
  },
  ...baseSchema,
});

module.exports = videocommentSchema;

model\videoModel.js

视频 Model 新增评论数字段

javascript 复制代码
 // 评论数
  commentCount: {
    type: Number,
    default: 0,
  },

controller\videoController.js

处理新增评论

javascript 复制代码
// 发表视频评论
exports.comment = async (req, res) => {
  var { videoId } = req.params;
  var { content } = req.body;
  var userId = req.user.user._id;
  try {
    var video = await Video.findById(videoId);
    if (!video) {
      return res.status(404).send({
        code: 404,
        message: '视频不存在',
      });
    }
    const videocommentModel = new Videocomment({
      content,
      video: videoId,
      user: userId,
    });
    var dbBack = await videocommentModel.save();
    // 评论数加1
    video.commentCount += 1;
    await video.save();
    res.status(201).send({
      code: 201,
      message: '评论成功',
      data: dbBack,
    });
  } catch (error) {
    console.log('error', error);
    res.status(500).send({
      code: 500,
      message: '评论失败',
      data: error,
    });
  }
};
视频评论列表与删除评论
视频评论列表

router\video.js

定义视频评论列表路由

javascript 复制代码
// 视频评论列表
router.post(
  '/commentlist/:videoId',
  verifyToken(false),
  videoController.commentlist
);

controller\videoController.js

实现视频评论列表逻辑

javascript 复制代码
// 获取分页视频评论列表
exports.commentlist = async (req, res) => {
  var { videoId } = req.params;
  var { page = 1, size = 10 } = req.body;
  page = parseInt(page);
  size = parseInt(size);
  try {
    var dbBack = await Videocomment.find({ video: videoId })
      .skip((page - 1) * size)
      .limit(size)
      .sort({
        createdAt: -1,
      })
      .populate('user');
    // 评论总条数
    var total = await Videocomment.countDocuments({ video: videoId });
    res.status(200).send({
      code: 200,
      message: '视频评论列表获取成功',
      data: dbBack,
      total: total,
    });
  } catch (error) {
    res.status(500).send({
      code: 500,
      message: '视频评论列表获取失败',
      data: error,
    });
  }
};
删除评论

router\video.js

定义删除评论路由

javascript 复制代码
// 删除评论
router.delete(
  '/comment/:videoId/:commentId',
  verifyToken(),
  videoController.deleteComment
);

controller\videoController.js

处理删除视频评论逻辑

javascript 复制代码
// 删除视频评论
exports.deleteComment = async (req, res) => {
  var { videoId, commentId } = req.params;
  var userId = req.user.user._id;
  try {
    var video = await Video.findById(videoId);
    if (!video) {
      return res.status(404).send({
        code: 404,
        message: '视频不存在',
      });
    }
    var videocomment = await Videocomment.findById(commentId);
    if (!videocomment) {
      return res.status(404).send({
        code: 404,
        message: '评论不存在',
      });
    }
    if (videocomment.user.toString() !== userId.toString()) {
      return res.status(403).send({
        code: 403,
        message: '无权限删除该评论',
      });
    }
    await Videocomment.deleteOne({ _id: commentId });
    // 评论数减1
    video.commentCount -= 1;
    await video.save();
    res.status(200).send({
      code: 200,
      message: '评论删除成功',
    });
  } catch (error) {
    res.status(500).send({
      code: 500,
      message: '评论删除失败',
      data: error,
    });
  }
};
喜欢与不喜欢视频
点赞视频

router\video.js

javascript 复制代码
// 点赞视频
router.post('/like/:videoId', verifyToken(), videoController.likeVideo);

model\videolikeModel.js

视频点赞 Model

javascript 复制代码
const mongoose = require('mongoose');
const baseSchema = require('./baseModel');

const videolikeSchema = new mongoose.Schema({
  video: {
    type: mongoose.Schema.Types.ObjectId,
    required: true,
    ref: 'Video',
  },
  user: {
    type: mongoose.Schema.Types.ObjectId,
    required: true,
    ref: 'User',
  },
  like: {
    type: Number,
    required: true,
    enum: [1, -1],
  },
  ...baseSchema,
});

module.exports = videolikeSchema;

model\videoModel.js

新增点赞数和取消点赞数

javascript 复制代码
  // 点赞数
  likeCount: {
    type: Number,
    default: 0,
  },
  // 踩数
  disLikeCount: {
    type: Number,
    default: 0,
  },

controller\videoController.js

处理点赞逻辑

javascript 复制代码
// 点赞视频
exports.likeVideo = async (req, res) => {
  var { videoId } = req.params;
  var userId = req.user.user._id;
  try {
    var video = await Video.findById(videoId);
    if (!video) {
      return res.status(404).send({
        code: 404,
        message: '视频不存在',
      });
    }
    // 检查用户是否已经点赞过该视频
    let islike = true;
    var doc = await Videolike.findOne({ video: videoId, user: userId });
    if (doc && doc.like === 1) {
      // 用户已经点赞,取消点赞
      await doc.remove();
      islike = false;
    } else if (doc && doc.like === -1) {
      // 用户已经点踩,修改为点赞
      doc.like = 1;
      await doc.save();
    } else {
      // 用户未点赞,创建新的点赞记录
      const videolikeModel = new Videolike({
        video: videoId,
        user: userId,
        like: 1,
      });
      await videolikeModel.save();
    }

    // 点赞数和踩数
    var likeCount = await Videolike.countDocuments({ video: videoId, like: 1 });
    var dislikeCount = await Videolike.countDocuments({
      video: videoId,
      like: -1,
    });
    video.likeCount = likeCount;
    video.dislikeCount = dislikeCount;
    await video.save();
    res.status(200).send({
      code: 200,
      message: '点赞成功',
      data: {
        ...video.toJSON(),
        islike: islike,
      },
    });
  } catch (error) {
    res.status(500).send({
      code: 500,
      message: '点赞失败',
      data: error,
    });
  }
};
取消喜欢视频

router\video.js

javascript 复制代码
// 点踩视频
router.post('/dislike/:videoId', verifyToken(), videoController.dislikeVideo);

controller\videoController.js

javascript 复制代码
// 点踩视频
exports.dislikeVideo = async (req, res) => {
  var { videoId } = req.params;
  var userId = req.user.user._id;
  try {
    var video = await Video.findById(videoId);
    if (!video) {
      return res.status(404).send({
        code: 404,
        message: '视频不存在',
      });
    } // 检查用户是否已经点踩过该视频
    let isdislike = true;
    var doc = await Videolike.findOne({ video: videoId, user: userId });
    if (doc && doc.like === -1) {
      // 用户已经点踩,取消点踩
      await doc.remove();
    } else if (doc && doc.like === 1) {
      // 用户已经点赞,修改为点踩
      doc.like = -1;
      await doc.save();
      isdislike = false;
    } else {
      // 用户未点踩,创建新的点踩记录
      const videolikeModel = new Videolike({
        video: videoId,
        user: userId,
        like: -1,
      });
      await videolikeModel.save();
      isdislike = false;
    }
    // 点赞数和踩数
    var likeCount = await Videolike.countDocuments({ video: videoId, like: 1 });
    var dislikeCount = await Videolike.countDocuments({
      video: videoId,
      like: -1,
    });
    video.likeCount = likeCount;
    video.dislikeCount = dislikeCount;
    await video.save();
    res.status(200).send({
      code: 200,
      message: '点踩成功',
      data: {
        ...video.toJSON(),
        isdislike: isdislike,
      },
    });
  } catch (error) {
    res.status(500).send({
      code: 500,
      message: '点踩失败',
      data: error,
    });
  }
};
喜欢的视频列表及视频详情优化
喜欢的视频列表

router\video.js

新建喜欢的视频列表路由

javascript 复制代码
// 喜欢的视频列表
router.get('/likelist', verifyToken(), videoController.likelist);

controller\videoController.js

实现获取喜欢的视频列表逻辑

javascript 复制代码
// 分页获取喜欢的视频列表
exports.likelist = async (req, res) => {
  var userId = req.user.user._id;
  const { page = 1, pageSize = 10 } = req.query;
  try {
    var dbBack = await Videolike.find({ user: userId, like: 1 })
      .skip((page - 1) * pageSize)
      .limit(pageSize)
      .populate('video', '_id title vodvideoId user');
    // 喜欢的视频总数
    var total = await Videolike.countDocuments({ user: userId, like: 1 });
    res.status(200).send({
      code: 200,
      message: '获取喜欢视频列表成功',
      data: dbBack,
      total,
    });
  } catch (error) {
    res.status(500).send({
      code: 500,
      message: '获取喜欢视频列表失败',
      data: error,
    });
  }
};
视频详情优化

controller\videoController.js

获取是否喜欢此视频、踩视频、订阅视频作者

javascript 复制代码
// 获取视频详情
exports.videodetail = async (req, res) => {
  var { videoId } = req.params;
  try {
    var dbBack = await Video.findById(videoId).populate(
      'user',
      '_id username cover'
    );
    if (dbBack) {
      var videoInfo = dbBack.toJSON();
      videoInfo.isLike = false;
      videoInfo.isDisLike = false;
      videoInfo.isSubscribe = false;

      // 是否登录
      if (req.user) {
        const userId = req.user.user._id;
        // 是否喜欢此视频
        videoInfo.isLike = (await Videolike.findOne({
          video: videoId,
          user: userId,
          like: 1,
        }))
          ? 1
          : -1;
        // 是否踩此视频
        videoInfo.isDisLike = (await Videolike.findOne({
          video: videoId,
          user: userId,
          like: -1,
        }))
          ? 1
          : -1;
        // 是否关注视频作者
        videoInfo.isSubscribe = (await Follow.findOne({
          user: userId,
          channle: videoInfo.user._id,
        }))
          ? 1
          : -1;
      }

      res.status(200).send({
        code: 200,
        message: '视频详情获取成功',
        data: videoInfo,
      });
    } else {
      res.status(404).send({
        code: 404,
        message: '视频不存在',
      });
    }
  } catch (error) {
    console.log('error', error);
    res.status(500).send({
      code: 500,
      message: '视频详情获取失败',
      data: error,
    });
  }
};
相关推荐
z***67771 小时前
macOS安装Redis
数据库·redis·macos
YJlio1 小时前
[编程达人挑战赛] 用 PowerShell 写了一个“电脑一键初始化脚本”:从混乱到可复制的开发环境
数据库·人工智能·电脑
x***13391 小时前
MySQL 篇 - Java 连接 MySQL 数据库并实现数据交互
java·数据库·mysql
v***91301 小时前
PostgreSQL 中进行数据导入和导出
大数据·数据库·postgresql
HIT_Weston1 小时前
45、【Ubuntu】【Gitlab】拉出内网 Web 服务:http.server 分析(二)
前端·http·gitlab
c***21291 小时前
oracle使用PLSQL导出表数据
数据库·oracle
q***33371 小时前
Spring boot启动原理及相关组件
数据库·spring boot·后端
合作小小程序员小小店1 小时前
桌面开发,在线%医院管理%系统,基于vs2022,c#,winform,sql server数据
开发语言·数据库·sql·microsoft·c#
十一.3661 小时前
79-82 call和apply,arguments,Date对象,Math
开发语言·前端·javascript