活动报名与缴费小程序开发笔记一

news2024/12/26 21:44:00

项目背景

活动报名与缴费小程序的开发背景主要源于以下几个因素:

  • 1.数字化时代的需求: 随着移动互联网和智能手机的普及,人们习惯使用手机进行各种活动。传统的纸质报名表格和线下缴费方式变得相对繁琐,而数字化报名与缴费小程序提供了更便捷的解决方案。
  • 2.提高效率和减少人力成本: 对于活动举办者来说,传统的报名方式需要大量人力去处理报名表格、确认报名信息以及核实支付情况。自动化的小程序可以大大提高工作效率,减少人力成本。
  • 3.提供更好的用户体验: 活动报名与缴费小程序可以提供更好的用户体验。用户可以随时随地浏览活动信息、完成报名和支付,无需前往实体场地或填写繁琐的纸质表格,从而提高了用户的满意度。
  • 4.安全性和可追溯性: 小程序通常具有安全性较高的支付系统,可以保障用户的支付安全。同时,所有的报名和支付记录都被数字化存储,方便活动举办者进行管理和核实。
  • 5.数据分析和营销需求: 数字化的报名与缴费系统可以收集大量的数据,包括参与人数、地域分布、支付方式偏好等。这些数据对于市场营销和活动策划非常有价值,可以帮助活动举办者更好地了解用户需求,制定更精准的营销策略。

综上所述,活动报名与缴费小程序的开发背景主要是为了适应数字化时代的需求,提高效率、提供更好的用户体验,增加安全性,以及为数据分析和市场营销提供支持。这种数字化解决方案在现代社会得到了广泛应用,并且不断发展壮大。

功能规划

  • 1 用户注册与登录:用户可以注册一个账号并使用该账号登录,以便管理他们的报名和支付信息。
  • 2 活动浏览与搜索:用户可以浏览发布的活动列表,并使用搜索功能查找感兴趣的活动。
  • 3 活动详情:用户可以点击活动列表中的活动,查看活动的详细信息,包括活动介绍、时间、地点、费用等。
  • 4 报名流程:用户可以选择要参加的活动,并填写报名表格。表格可能包括个人信息(姓名、联系方式,后台可以自定义)、参与人数、特殊要求等。
  • 5 支付功能:用户可以选择微信支付完成报名费用的支付。支付完成后,系统会生成报名确认凭证。
  • 6 用户报名管理:用户可以查看已经报名的活动列表,以及报名状态和详细信息。用户可能还可以编辑报名信息或取消已报名的活动。
  • 7 评价与反馈:用户可以对参加的活动进行评价和反馈。他们可以分享他们的活动体验,并提供改进建议。

功能示意图

在这里插入图片描述

数据库设计


ActivityModel.DB_STRUCTURE = {
    _pid: 'string|true',
    ACTIVITY_ID: 'string|true',

    ACTIVITY_TITLE: 'string|true|comment=标题',
    ACTIVITY_STATUS: 'int|true|default=1|comment=状态 0=未启用,1=使用中',

    ACTIVITY_CATE_ID: 'string|true|default=0|comment=分类',
    ACTIVITY_CATE_NAME: 'string|false|comment=分类冗余',

    ACTIVITY_CANCEL_SET: 'int|true|default=1|comment=取消设置 0=不允,1=允许,2=仅截止前可取消',
    ACTIVITY_CHECK_SET: 'int|true|default=0|comment=审核 0=不需要审核,1=需要审核',
    ACTIVITY_IS_MENU: 'int|true|default=1|comment=是否公开展示名单',

    ACTIVITY_MAX_CNT: 'int|true|default=20|comment=人数上限 0=不限',
    ACTIVITY_START: 'int|false|comment=开始时间戳',
	ACTIVITY_END: 'int|false|comment=截止时间戳',
	ACTIVITY_START_DAY: 'string|false|comment=开始时间',
    ACTIVITY_END_DAY: 'string|false|comment=截止时间',
	ACTIVITY_STOP: 'int|true|default=0|comment=报名截止时间 0=永不过期',

	ACTIVITY_START_MONTH: 'string|false|comment=开始月份',
	ACTIVITY_END_MONTH: 'string|false|comment=截止月份',

    ACTIVITY_ORDER: 'int|true|default=9999',
    ACTIVITY_VOUCH: 'int|true|default=0',

    ACTIVITY_FORMS: 'array|true|default=[]',
    ACTIVITY_OBJ: 'object|true|default={}',

    ACTIVITY_JOIN_FORMS: 'array|true|default=[]',

    ACTIVITY_ADDRESS: 'string|false|comment=详细地址',
    ACTIVITY_ADDRESS_GEO: 'object|false|comment=详细地址坐标参数',

    ACTIVITY_QR: 'string|false',
    ACTIVITY_VIEW_CNT: 'int|true|default=0',
    ACTIVITY_COMMENT_CNT: 'int|true|default=0',

    ACTIVITY_METHOD: 'int|true|default=0|comment=支付方式 0=线下,1=线上',
    ACTIVITY_FEE: 'int|false|comment=支付金额 分',

    ACTIVITY_JOIN_CNT: 'int|true|default=0',
    ACTIVITY_PAY_CNT: 'int|true|default=0|comment=支付数',
    ACTIVITY_PAY_FEE: 'int|true|default=0|comment=支付额',

    ACTIVITY_USER_LIST: 'array|true|default=[]|comment={name,id,pic}',

    ACTIVITY_ADD_TIME: 'int|true',
    ACTIVITY_EDIT_TIME: 'int|true',
    ACTIVITY_ADD_IP: 'string|false',
    ACTIVITY_EDIT_IP: 'string|false',
};

ActivityJoinModel.DB_STRUCTURE = {
    _pid: 'string|true',
    ACTIVITY_JOIN_ID: 'string|true',
    ACTIVITY_JOIN_ACTIVITY_ID: 'string|true|comment=报名PK',

    ACTIVITY_JOIN_IS_ADMIN: 'int|true|default=0|comment=是否管理员添加 0/1',

    ACTIVITY_JOIN_CODE: 'string|true|comment=核验码15位',
    ACTIVITY_JOIN_IS_CHECKIN: 'int|true|default=0|comment=是否签到 0/1 ',
    ACTIVITY_JOIN_CHECKIN_TIME: 'int|false|default=0|签到时间',
    ACTIVITY_JOIN_CANCEL_TIME: 'int|true|default=0|comment=取消时间',

    ACTIVITY_JOIN_USER_ID: 'string|true|comment=用户ID',


    ACTIVITY_JOIN_FORMS: 'array|true|default=[]|comment=表单',
    ACTIVITY_JOIN_OBJ: 'object|true|default={}',

    ACTIVITY_JOIN_STATUS: 'int|true|default=1|comment=状态  0=待审核 1=报名成功, 98=自己取消,99=审核未过/取消',
    ACTIVITY_JOIN_REASON: 'string|false|comment=审核拒绝或者取消理由',


    ACTIVITY_JOIN_FEE: 'int|true|default=0|comment=需支付费用 分',

    ACTIVITY_JOIN_PAY_TRADE_NO: 'string|false|comment=商家订单号 32位',
    ACTIVITY_JOIN_PAY_STATUS: 'int|true|default=0|comment=支付状态 0=未支付 1=已支付 8=已退款 99=无需支付',
    ACTIVITY_JOIN_PAY_FEE: 'int|true|default=0|comment=已支付费用 分',
    ACTIVITY_JOIN_PAY_TIME: 'int|true|default=0|comment=支付时间',

    ACTIVITY_JOIN_ADD_TIME: 'int|true',
    ACTIVITY_JOIN_EDIT_TIME: 'int|true',
    ACTIVITY_JOIN_ADD_IP: 'string|false',
    ACTIVITY_JOIN_EDIT_IP: 'string|false',
};

难点攻关

class ActivityService extends BaseProjectService {

	async minuteJob() {
		console.log('### minuteJob >>>>>');


		// 未支付的成功订单取消  
		let time = this._timestamp - 6 * 60 * 1000;
		console.log('###### Begin>>> 未支付订单6分钟后取消, time<=' + time + ', ' + timeUtil.timestamp2Time(time));


		let where = {
			ACTIVITY_JOIN_STATUS: ['in', [ActivityJoinModel.STATUS.WAIT, ActivityJoinModel.STATUS.SUCC]],
			ACTIVITY_JOIN_PAY_STATUS: 0,
			ACTIVITY_JOIN_ADD_TIME: ['<=', time],
		}
		let rows = await ActivityJoinModel.getAll(where, '*', {}, 3000, false);
		console.log('未支付订单6分钟后取消, count=', rows.length);

		for (let k in rows) {
			let activityJoin = rows[k];

			let tradeNo = activityJoin.ACTIVITY_JOIN_PAY_TRADE_NO;

			if (!await this.fixActivityJoinPay(tradeNo, activityJoin.ACTIVITY_JOIN_ACTIVITY_ID)) {
				console.log('该报名记录未支付,已取消并删除!', activityJoin);
			}

		}

		console.log('###### END. 未支付订单6分钟后取消');

	}

	// 获取当前活动状态
	getJoinStatusDesc(activity) {
		let timestamp = this._timestamp;

		if (activity.ACTIVITY_STATUS == 0)
			return '活动停止';
		else if (activity.ACTIVITY_END <= timestamp)
			return '活动结束';
		else if (activity.ACTIVITY_STOP <= timestamp)
			return '报名结束';
		else if (activity.ACTIVITY_MAX_CNT > 0
			&& activity.ACTIVITY_JOIN_CNT >= activity.ACTIVITY_MAX_CNT)
			return '报名已满';
		else
			return '报名中';
	}

	/** 浏览信息 */
	async viewActivity(userId, id) {

		await this.fixUserActivityJoinPayRecord(userId);

		let fields = '*';

		let where = {
			_id: id,
			ACTIVITY_STATUS: ActivityModel.STATUS.COMM
		}
		let activity = await ActivityModel.getOne(where, fields);
		if (!activity) return null;

		ActivityModel.inc(id, 'ACTIVITY_VIEW_CNT', 1);

		// 判断是否有报名
		let whereJoin = {
			ACTIVITY_JOIN_USER_ID: userId,
			ACTIVITY_JOIN_ACTIVITY_ID: id,
			ACTIVITY_JOIN_STATUS: ['in', [ActivityJoinModel.STATUS.WAIT, ActivityJoinModel.STATUS.SUCC]]
		}
		let activityJoin = await ActivityJoinModel.getOne(whereJoin);
		if (activityJoin) {
			activity.myActivityJoinId = activityJoin._id;
			activity.myActivityJoinTag = (activityJoin.ACTIVITY_JOIN_STATUS == ActivityJoinModel.STATUS.WAIT) ? '待审核' : '已报名';
			if (activity.myActivityJoinTag == '已报名' && activityJoin.ACTIVITY_JOIN_PAY_STATUS == 1) {
				activity.myActivityJoinTag = '已报名缴费';
			}
		}
		else {
			activity.myActivityJoinId = '';
			activity.myActivityJoinTag = '';
		}


		return activity;
	}

	/** 取得分页列表 */
	async getActivityList({
		cateId, //分类查询条件
		search, // 搜索条件
		sortType, // 搜索菜单
		sortVal, // 搜索菜单
		orderBy, // 排序 
		page,
		size,
		isTotal = true,
		oldTotal
	}) {

		orderBy = orderBy || {
			'ACTIVITY_ORDER': 'asc',
			'ACTIVITY_ADD_TIME': 'desc'
		};
		let fields = 'ACTIVITY_START_MONTH,ACTIVITY_END_MONTH,ACTIVITY_START_DAY,ACTIVITY_END_DAY,ACTIVITY_USER_LIST,ACTIVITY_STOP,ACTIVITY_JOIN_CNT,ACTIVITY_OBJ,ACTIVITY_VIEW_CNT,ACTIVITY_TITLE,ACTIVITY_MAX_CNT,ACTIVITY_START,ACTIVITY_END,ACTIVITY_ORDER,ACTIVITY_STATUS,ACTIVITY_CATE_NAME,ACTIVITY_OBJ';

		let where = {};
		where.and = {
			_pid: this.getProjectId() //复杂的查询在此处标注PID
		};
		if (cateId && cateId !== '0') where.and.ACTIVITY_CATE_ID = cateId;

		where.and.ACTIVITY_STATUS = ActivityModel.STATUS.COMM; // 状态  


		if (util.isDefined(search) && search) {
			where.or = [{
				ACTIVITY_TITLE: ['like', search]
			},];
		} else if (sortType && util.isDefined(sortVal)) {
			// 搜索菜单
			switch (sortType) {
				case 'cateId': {
					if (sortVal) where.and.ACTIVITY_CATE_ID = String(sortVal);
					break;
				}
				case 'sort': {
					// 排序
					orderBy = this.fmtOrderBySort(sortVal, 'ACTIVITY_ADD_TIME');
					break;
				}
				case 'today': { //今天
					let time = timeUtil.time('Y-M-D');
					where.and.ACTIVITY_START_DAY = ['<=', time];
					where.and.ACTIVITY_END_DAY = ['>=', time];
					break;
				}
				case 'tomorrow': { //明日
					let time = timeUtil.time('Y-M-D', 86400);
					where.and.ACTIVITY_START_DAY = ['<=', time];
					where.and.ACTIVITY_END_DAY = ['>=', time];
					break;
				}
				case 'month': { //本月
					let month = timeUtil.time('Y-M');
					where.and.ACTIVITY_START_MONTH = ['<=', month];
					where.and.ACTIVITY_END_MONTH = ['>=', month];
					break;
				}
			}
		}

		return await ActivityModel.getList(where, fields, orderBy, page, size, isTotal, oldTotal);
	}


	/** 取得某一个报名分页列表 */
	async getActivityJoinList(activityId, {
		search, // 搜索条件
		sortType, // 搜索菜单
		sortVal, // 搜索菜单
		orderBy, // 排序 
		page,
		size,
		isTotal = true,
		oldTotal
	}) {
		orderBy = orderBy || {
			'ACTIVITY_JOIN_ADD_TIME': 'desc'
		};
		let fields = 'ACTIVITY_JOIN_OBJ,ACTIVITY_JOIN_IS_CHECKIN,ACTIVITY_JOIN_REASON,ACTIVITY_JOIN_ACTIVITY_ID,ACTIVITY_JOIN_STATUS,ACTIVITY_JOIN_ADD_TIME,user.USER_PIC,user.USER_NAME,user.USER_OBJ';

		let where = {
			ACTIVITY_JOIN_ACTIVITY_ID: activityId,
			ACTIVITY_JOIN_STATUS: ActivityModel.STATUS.COMM
		};

		let joinParams = {
			from: UserModel.CL,
			localField: 'ACTIVITY_JOIN_USER_ID',
			foreignField: 'USER_MINI_OPENID',
			as: 'user',
		};

		let result = await ActivityJoinModel.getListJoin(joinParams, where, fields, orderBy, page, size, isTotal, oldTotal);

		return result;
	}


	/** 取得我的报名分页列表 */
	async getMyActivityJoinList(userId, {
		search, // 搜索条件
		sortType, // 搜索菜单
		sortVal, // 搜索菜单
		orderBy, // 排序 
		page,
		size,
		isTotal = true,
		oldTotal
	}) {

		await this.fixUserActivityJoinPayRecord(userId);

		orderBy = orderBy || {
			'ACTIVITY_JOIN_ADD_TIME': 'desc'
		};
		let fields = 'ACTIVITY_JOIN_PAY_STATUS,ACTIVITY_JOIN_IS_CHECKIN,ACTIVITY_JOIN_REASON,ACTIVITY_JOIN_ACTIVITY_ID,ACTIVITY_JOIN_STATUS,ACTIVITY_JOIN_ADD_TIME,activity.ACTIVITY_END,activity.ACTIVITY_START,activity.ACTIVITY_TITLE';

		let where = {
			ACTIVITY_JOIN_USER_ID: userId
		};

		if (util.isDefined(search) && search) {
			where['activity.ACTIVITY_TITLE'] = {
				$regex: '.*' + search,
				$options: 'i'
			};
		} else if (sortType) {
			// 搜索菜单
			switch (sortType) {
				case 'timedesc': { //按时间倒序
					orderBy = {
						'activity.ACTIVITY_START': 'desc',
						'ACTIVITY_JOIN_ADD_TIME': 'desc'
					};
					break;
				}
				case 'timeasc': { //按时间正序
					orderBy = {
						'activity.ACTIVITY_START': 'asc',
						'ACTIVITY_JOIN_ADD_TIME': 'asc'
					};
					break;
				}
				case 'succ': {
					where.ACTIVITY_JOIN_STATUS = ActivityJoinModel.STATUS.SUCC;
					break;
				}
				case 'wait': {
					where.ACTIVITY_JOIN_STATUS = ActivityJoinModel.STATUS.WAIT;
					break;
				}
				case 'usercancel': {
					where.ACTIVITY_JOIN_STATUS = ActivityJoinModel.STATUS.CANCEL;
					break;
				}
				case 'cancel': {
					where.ACTIVITY_JOIN_STATUS = ActivityJoinModel.STATUS.ADMIN_CANCEL;
					break;
				}
			}
		}

		let joinParams = {
			from: ActivityModel.CL,
			localField: 'ACTIVITY_JOIN_ACTIVITY_ID',
			foreignField: '_id',
			as: 'activity',
		};

		let result = await ActivityJoinModel.getListJoin(joinParams, where, fields, orderBy, page, size, isTotal, oldTotal);

		return result;
	}

	/** 取得我的报名详情 */
	async getMyActivityJoinDetail(userId, activityJoinId) {

		let fields = '*';

		let where = {
			_id: activityJoinId,
			ACTIVITY_JOIN_USER_ID: userId
		};
		let activityJoin = await ActivityJoinModel.getOne(where, fields);
		if (activityJoin) {
			activityJoin.activity = await ActivityModel.getOne(activityJoin.ACTIVITY_JOIN_ACTIVITY_ID, 'ACTIVITY_TITLE,ACTIVITY_START,ACTIVITY_END');
		}
		return activityJoin;
	}

	// 修正某用户所有未支付的成功订单状态,无须支付的不用处理
	async fixUserActivityJoinPayRecord(userId) {
		let where = {
			ACTIVITY_JOIN_USER_ID: userId,
			ACTIVITY_JOIN_PAY_STATUS: 0,
			ACTIVITY_JOIN_STATUS: ['in', [ActivityJoinModel.STATUS.WAIT, ActivityJoinModel.STATUS.SUCC]],
		}
		let list = await ActivityJoinModel.getAll(where);

		for (let k = 0; k < list.length; k++) {
			await this.fixActivityJoinPay(list[k].ACTIVITY_JOIN_PAY_TRADE_NO, list[k].ACTIVITY_JOIN_ACTIVITY_ID);
		}
	}

	// 修正某订单状态 (仅需支付订单)
	async fixActivityJoinPay(tradeNo, activityId) {

		if (!tradeNo) {
			// 无支付号空单 删除
			await ActivityJoinModel.del({ ACTIVITY_JOIN_PAY_TRADE_NO: tradeNo });

			// 重新统计
			this.statActivityJoin(activityId);

			return false;
		}

		let payService = new PayService();
		if (!await payService.fixPayResult(tradeNo)) {
			// 关闭未支付单
			payService.closePay(tradeNo);

			// 未支付 
			await ActivityJoinModel.del({ ACTIVITY_JOIN_PAY_TRADE_NO: tradeNo });

			// 重新统计
			this.statActivityJoin(activityId);

			return false;
		}

		// 已支付
		let pay = await PayModel.getOne({ PAY_TRADE_NO: tradeNo });
		if (!pay) this.AppError('支付流水异常,请核查');

		// 更新支付信息
		let data = {
			ACTIVITY_JOIN_PAY_STATUS: 1,
			ACTIVITY_JOIN_PAY_TRADE_NO: tradeNo,
			ACTIVITY_JOIN_PAY_FEE: pay.PAY_TOTAL_FEE,
			ACTIVITY_JOIN_PAY_TIME: pay.PAY_END_TIME,
		}
		await ActivityJoinModel.edit({ ACTIVITY_JOIN_PAY_TRADE_NO: tradeNo }, data);


		// 重新统计
		this.statActivityJoin(activityId);
		return true;
	}

	//################## 报名  
	async statActivityJoin(activityId) {
		// 报名数
		let where = {
			ACTIVITY_JOIN_ACTIVITY_ID: activityId,
			ACTIVITY_JOIN_STATUS: ['in', [ActivityJoinModel.STATUS.WAIT, ActivityJoinModel.STATUS.SUCC]]
		}
		let cnt = await ActivityJoinModel.count(where);


		// 已支付记录
		let wherePayCnt = {
			ACTIVITY_JOIN_ACTIVITY_ID: activityId,
			ACTIVITY_JOIN_PAY_STATUS: 1,
			ACTIVITY_JOIN_STATUS: ['in', [ActivityJoinModel.STATUS.WAIT, ActivityJoinModel.STATUS.SUCC]]
		}
		let payCnt = await ActivityJoinModel.count(wherePayCnt);


		// 已支付金额
		let wherePayFee = {
			ACTIVITY_JOIN_ACTIVITY_ID: activityId,
			ACTIVITY_JOIN_PAY_STATUS: 1,
			ACTIVITY_JOIN_STATUS: ['in', [ActivityJoinModel.STATUS.WAIT, ActivityJoinModel.STATUS.SUCC]]
		}
		let payFee = await ActivityJoinModel.sum(wherePayFee, 'ACTIVITY_JOIN_PAY_FEE');


		// 报名用户头像列表
		let whereUserList = {
			ACTIVITY_JOIN_ACTIVITY_ID: activityId,
			ACTIVITY_JOIN_STATUS: ActivityJoinModel.STATUS.SUCC,
			ACTIVITY_JOIN_PAY_STATUS: ['in', [1, 99]]
		}
		let joinParams = {
			from: UserModel.CL,
			localField: 'ACTIVITY_JOIN_USER_ID',
			foreignField: 'USER_MINI_OPENID',
			as: 'user',
		};
		let orderBy = {
			ACTIVITY_JOIN_ADD_TIME: 'desc'
		}
		let userList = await ActivityJoinModel.getListJoin(joinParams, whereUserList, 'ACTIVITY_JOIN_ADD_TIME,user.USER_MINI_OPENID,user.USER_NAME,user.USER_PIC', orderBy, 1, 6, false, 0);
		userList = userList.list;

		for (let k = 0; k < userList.length; k++) {
			userList[k] = userList[k].user;
		}

		let data = {
			ACTIVITY_JOIN_CNT: cnt,
			ACTIVITY_PAY_CNT: payCnt,
			ACTIVITY_PAY_FEE: payFee,

			ACTIVITY_USER_LIST: userList
		}
		await ActivityModel.edit(activityId, data);
	}

	/**  报名前获取关键信息 */
	async detailForActivityJoin(userId, activityId) {

		await this.fixUserActivityJoinPayRecord(userId);

		let fields = 'ACTIVITY_JOIN_FORMS, ACTIVITY_TITLE, ACTIVITY_FEE, ACTIVITY_METHOD';

		let where = {
			_id: activityId,
			ACTIVITY_STATUS: ActivityModel.STATUS.COMM
		}
		let activity = await ActivityModel.getOne(where, fields);
		if (!activity)
			this.AppError('该活动不存在');

		let whereMy = {
			ACTIVITY_JOIN_USER_ID: userId,
		}
		let orderByMy = {
			ACTIVITY_JOIN_ADD_TIME: 'desc'
		}

		//***取得本人所有记录
		let joinList = await ActivityJoinModel.getAll(whereMy, 'ACTIVITY_JOIN_OBJ,ACTIVITY_JOIN_FORMS', orderByMy);
		let addressList = [];
		let addressList2 = [];
		for (let k = 0; k < joinList.length; k++) {
			let exist = false;
			for (let j = 0; j < addressList.length; j++) {
				if (addressList[j].name === joinList[k].ACTIVITY_JOIN_OBJ.name) {
					exist = true;
					break;
				}
			}

			if (!exist) {
				addressList.push(joinList[k].ACTIVITY_JOIN_OBJ);
				addressList2.push(joinList[k].ACTIVITY_JOIN_FORMS);
			}
		}

		// 取出本人最近一次的填写表单
		let joinMy = await ActivityJoinModel.getOne(whereMy, 'ACTIVITY_JOIN_FORMS', orderByMy);
		joinMy = null;

		let myForms = joinMy ? joinMy.ACTIVITY_JOIN_FORMS : [];
		activity.myForms = myForms;

		activity.addressList = addressList;
		activity.addressList2 = addressList2;

		activity.ACTIVITY_FEE = Number(dataUtil.fmtMoney(activity.ACTIVITY_FEE / 100));

		return activity;
	}

	/** 取消我的报名 只有成功和待审核可以取消   */
	async cancelMyActivityJoin(userId, activityJoinId) {
		let where = {
			_id: activityJoinId,
			ACTIVITY_JOIN_USER_ID: userId,
			ACTIVITY_JOIN_STATUS: ['in', [ActivityJoinModel.STATUS.WAIT, ActivityJoinModel.STATUS.SUCC]] //只有成功和待审核可以取消
		};
		let activityJoin = await ActivityJoinModel.getOne(where);

		if (!activityJoin) {
			this.AppError('未找到可取消的报名记录');
		}

		if (activityJoin.ACTIVITY_JOIN_IS_CHECKIN == 1)
			this.AppError('该活动已经签到,无法取消报名');

		let activity = await ActivityModel.getOne(activityJoin.ACTIVITY_JOIN_ACTIVITY_ID);
		if (!activity)
			this.AppError('该活动不存在');

		if (activity.ACTIVITY_END <= this._timestamp)
			this.AppError('该活动已经结束,无法取消');

		if (activity.ACTIVITY_CANCEL_SET == 0)
			this.AppError('该活动不能取消报名');

		if (activity.ACTIVITY_CANCEL_SET == 2 && activity.ACTIVITY_STOP < this._timestamp)
			this.AppError('该活动已经截止报名,不能取消');

		if (activityJoin.ACTIVITY_JOIN_PAY_STATUS == 99) {
			// 无须支付
			// 更新记录 
			let data = {
				ACTIVITY_JOIN_STATUS: ActivityJoinModel.STATUS.CANCEL,
				ACTIVITY_JOIN_CANCEL_TIME: this._timestamp,
			}
			await ActivityJoinModel.edit(activityJoinId, data);
		}
		else {
			let tradeNo = activityJoin.ACTIVITY_JOIN_PAY_TRADE_NO;
			if (!await this.fixActivityJoinPay(tradeNo, activityJoin.ACTIVITY_JOIN_ACTIVITY_ID)) {
				this.AppError('该报名记录未支付,已取消并删除!');
			}
			let payService = new PayService();
			await payService.refundPay(tradeNo, '用户取消报名');

			// 更新记录 
			let data = {
				ACTIVITY_JOIN_STATUS: ActivityJoinModel.STATUS.CANCEL,
				ACTIVITY_JOIN_CANCEL_TIME: this._timestamp,
				ACTIVITY_JOIN_PAY_STATUS: 8,
			}
			await ActivityJoinModel.edit(activityJoinId, data);
		}


		// 统计
		await this.statActivityJoin(activityJoin.ACTIVITY_JOIN_ACTIVITY_ID);
	}


	/** 用户自助签到 */
	async myJoinSelf(userId, activityId) {
		let activity = await ActivityModel.getOne(activityId);
		if (!activity)
			this.AppError('活动不存在或者已经关闭');

		let day = timeUtil.timestamp2Time(activity.ACTIVITY_START, 'Y-M-D');

		let today = timeUtil.time('Y-M-D');
		if (day != today)
			this.AppError('仅在活动当天可以签到,当前签到码的日期是' + day);

		let whereSucc = {
			ACTIVITY_JOIN_USER_ID: userId,
			ACTIVITY_JOIN_STATUS: ActivityJoinModel.STATUS.SUCC
		}
		let cntSucc = await ActivityJoinModel.count(whereSucc);

		let whereCheckin = {
			ACTIVITY_JOIN_USER_ID: userId,
			ACTIVITY_JOIN_IS_CHECKIN: 1,
			ACTIVITY_JOIN_STATUS: ActivityJoinModel.STATUS.SUCC
		}
		let cntCheckin = await ActivityJoinModel.count(whereCheckin);

		let ret = '';
		if (cntSucc == 0) {
			ret = '您没有本次活动报名成功的记录,请在「个人中心 - 我的活动报名」查看详情~';
		} else if (cntSucc == cntCheckin) {
			// 同一活动多次报名的情况
			ret = '您已签到,无须重复签到,请在「个人中心 - 我的活动报名」查看详情~';
		} else {
			let where = {
				ACTIVITY_JOIN_USER_ID: userId,
				ACTIVITY_JOIN_IS_CHECKIN: 0,
				ACTIVITY_JOIN_STATUS: ActivityJoinModel.STATUS.SUCC
			}
			let data = {
				ACTIVITY_JOIN_IS_CHECKIN: 1,
				ACTIVITY_JOIN_CHECKIN_TIME: this._timestamp,
			}
			await ActivityJoinModel.edit(where, data);
			ret = '签到成功,请在「个人中心 - 我的活动报名」查看详情~'
		}
		return {
			ret
		};
	}

	/** 按天获取报名项目 */
	async getActivityListByDay(day) {
		let start = timeUtil.time2Timestamp(day);
		let end = start + 86400 * 1000 - 1;
		let where = {
			ACTIVITY_STATUS: ActivityModel.STATUS.COMM,
			ACTIVITY_START: ['between', start, end],
		};

		let orderBy = {
			'ACTIVITY_ORDER': 'asc',
			'ACTIVITY_ADD_TIME': 'desc'
		};

		let fields = 'ACTIVITY_TITLE,ACTIVITY_START,ACTIVITY_OBJ.cover';

		let list = await ActivityModel.getAll(where, fields, orderBy);

		let retList = [];

		for (let k = 0; k < list.length; k++) {

			let node = {};
			node.timeDesc = timeUtil.timestamp2Time(list[k].ACTIVITY_START, 'h:m');
			node.title = list[k].ACTIVITY_TITLE;
			node.pic = list[k].ACTIVITY_OBJ.cover[0];
			node._id = list[k]._id;
			retList.push(node);

		}
		return retList;
	}

	/**
	 * 获取从某天开始可报名的日期
	 * @param {*} fromDay  日期 Y-M-D
	 */
	async getActivityHasDaysFromDay(fromDay) {
		let where = {
			ACTIVITY_START: ['>=', timeUtil.time2Timestamp(fromDay)],
		};

		let fields = 'ACTIVITY_START';
		let list = await ActivityModel.getAllBig(where, fields);

		let retList = [];
		for (let k = 0; k < list.length; k++) {
			let day = timeUtil.timestamp2Time(list[k].ACTIVITY_START, 'Y-M-D');
			if (!retList.includes(day)) retList.push(day);
		}
		return retList;
	}


}

UI设计

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

后台设计

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述在这里插入图片描述
在这里插入图片描述

git源码

源码下载

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

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

相关文章

2023年-华为机试题库B卷(Python)【满分】

华为机试题库B卷 已于5月10号 更新为2023 B卷 &#xff08;2023-10-04 更新本文&#xff09; 华为机试有三道题目&#xff0c;前两道属于简单或中等题&#xff0c;分值为100分&#xff0c;第三道为中等或困难题&#xff0c;分值为200分。总分为 400 分&#xff0c;150分钟考试…

GKR+Groth16:更快的MiMC证明

1. 引言 Consensys团队Alexandre Belling等人2022年论文 Recursion over Public-Coin Interactive Proof Systems; Faster Hash Verification 中&#xff0c;提出了&#xff1a; 用GKR来证明MiMC哈希计算的完整性将GKR verifier嵌入到SNARK&#xff08;Groth16&#xff09;电…

【开发篇】十四、SpringBoot整合Quartz实现定时任务

文章目录 1、关于定时任务2、Java原生实现3、相关名词4、SpringBoot整合Quartz5、Quartz的通用配置6、关于QuartzJobBean7、关于调度器Scheduler的绑定8、Quartz持久化 1、关于定时任务 定时任务在实际开发中使用场景很多&#xff0c;比如&#xff1a; 年度报告各种统计报告某…

vs code 离线安装 CodeLLDB 包[Acquiring CodeLLDB platform package]

1. 问题描述 最近在配置使用vscode编译c&#xff0c;一打开vscode就弹出以下信息“Acquiring CodeLLDB platform package” 2. 问题原因 vscode在安装CodeLLDB插件时&#xff0c;速度太慢&#xff0c;一直不能成功 3. 解决方案&#xff1a; 离线下载 CodeLLDB插件&#xff0c…

前后端通信到底是怎样一个过程

前后端通信是怎样 前言&#xff1a;Http协议 超文本传输协议 规定&#xff1a;每一次前后端通信&#xff0c;前端需要主动向后端发出请求&#xff0c;后端接收到前端的请求后&#xff0c;可以给出响应 1、Http报文 浏览器向服务器发送请求时&#xff0c;请求本身就是信息&…

ROS导航——环境感知(激光雷达)

下载相关驱动包&#xff08;激光雷达厂商应该会给出&#xff09; 编译后可能会出现部分错误&#xff0c;以下是部分情况&#xff1a; &#xff08;1&#xff09; 移植功能包后出现c文件无法找到头文件的情况&#xff1a;解决链接 修改代码&#xff1a;&#xff08;以我的雷达为…

将pyc文件转换为py文件

1.首先将pip版本升级 pip install --upgrade pip 2.然后安装uncompyle6 pip install uncompyle6 3.在系统的环境变量中&#xff0c;添加“python_home” 4.在系统变量Path中添加&#xff1a; %python_home%\Scripts\ 5.运行下面的代码&#xff0c;就会在你.pyc对应文件夹…

腾讯云服务器完整建站过程(新手搭建网站教程)

使用腾讯云服务器搭建网站全流程&#xff0c;包括轻量应用服务器和云服务器CVM建站教程&#xff0c;轻量可以使用应用镜像一键建站&#xff0c;云服务器CVM可以通过安装宝塔面板的方式来搭建网站&#xff0c;腾讯云服务器网分享使用腾讯云服务器建站教程&#xff0c;新手站长搭…

第二章 进程与线程 十九、管程

目录 一、定义 管程是一种特殊的软件模块&#xff0c;由以下部分组成&#xff1a; 二、管程的基本特征 三、使用管程解决生产者消费者问题 四、总结 一、定义 管程是一种特殊的软件模块&#xff0c;由以下部分组成&#xff1a; 1、局部于管程的共享数据结构说明;&#xf…

[QT编程系列-45]: 内存检测工具Dr.Memory在Windows上的使用实践与详解

目录 一、使用前的澄清 二、下载地址 三、功能概述 四、 使用方法与步骤 4.1 常见命令 4.2 命令选项详解 4.3 常见问题监测 4.3.1 内存泄露相关参数 4.4 结果输出参数 4.5 输出分析 一、使用前的澄清 &#xff08;1&#xff09;之前在https://blog.csdn.net/fengbin…

SNAP与Sen2Cor下载与安装

SNAP软件下载与安装 一、下载地址 首先进入网站 找到DOWNLOAD下载页&#xff0c; 安装完成后&#xff0c;界面如下 还需要再装一个Sen2cor下载好之后&#xff0c;解压到用户文件夹下 然后打开L2A_Process.bat文件 打开CMD&#xff0c;输入 cd C:\Users\lenovo\AppData\L…

【算法训练-二分查找 一】【基本二分】二分查找、在排序数组中查找元素的第一个和最后一个位置

废话不多说&#xff0c;喊一句号子鼓励自己&#xff1a;程序员永不失业&#xff0c;程序员走向架构&#xff01;本篇Blog的主题是螺旋矩阵&#xff0c;使用【二维数组】这个基本的数据结构来实现 二分查找【EASY】 从最简单的二分查找入手&#xff0c;进而开始解决一系列其变体…

【Linux基础】Linux云服务器(腾讯云、阿里云、华为云)环境部署 | 安装远程XShell | 基本账号管理(超详细教程)

&#x1f449; 系列专栏&#xff1a;【LLinux基础】 &#x1f648; 个人主页&#xff1a;sunnyll 目录 一、前言 二、 Linux环境安装 &#x1f4a6; Linux 环境的搭建方式 &#x1f4a6;如何购买云服务器 三、 安装远程控制XShell &#x1f4a6;下载 XShell &#x1f4…

阿里云对象存储OSS SDK的使用

官方文档 https://help.aliyun.com/zh/oss/developer-reference/java 准备工作 windows安装好JDK&#xff0c;这里使用JDK1.8为例 windows安装好IDEA&#xff0c;这里使用IDEA2022 登录阿里云控制台&#xff0c;通过免费试用OSS或开通OSS 步骤 配置访问凭证 有临时和长期…

STM32F4学习笔记读取芯片UID和MAC地址

一、简介 在嵌入式设备开发过程中有时会需要为设备设置唯一的ID用以标识设备唯一&#xff0c;比如要求同一总线上的所有设备ID不能重复&#xff0c;要求设备具体唯一的MAC地址等等。每个STM32微控制器都自带一个96位的唯一ID&#xff0c;这个ID在任何情况下都是唯一且不允许修…

谷歌地球引擎GEE账户注册的快速、百分百成功方法

本文介绍免费注册谷歌地球引擎&#xff08;Google Earth Engine&#xff0c;GEE&#xff09;账户的方便、快捷的最新方法&#xff1b;基于这一方法&#xff0c;只要我们创建一个谷歌Cloud Project&#xff0c;就可以直接访问GEE。 GEE在原本&#xff08;大概前几年的时候&#…

day 10.4

服务器 头文件 #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QLine> #include <QTcpServer> #include <QTcpSocket> #include <QMessageBox>QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAMESPACEclass Wid…

Java中如何实现定时任务?

文章目录 定时任务基本介绍前言基本概念介绍线程类实现定时任务Thread类实现定时任务Runnable接口实现定时任务Callable接口实现定时任务 Timer实现定时任务Timer的常用方法Timer的优缺点schedule和scheduleAtFixedRate的区别代码示例 ScheduledExecutorService实现定时Schedul…

僵尸进程的产生与处理

僵尸进程&#xff08;Zombie Process&#xff09;是指在操作系统中已经完成了执行&#xff0c;但其父进程尚未调用wait()或waitpid()来获取其终止状态的子进程。当一个进程结束时&#xff0c;操作系统会保留该进程的一些基本信息&#xff0c;包括进程ID&#xff08;PID&#xf…