This commit is contained in:
张良(004796)
2024-03-08 14:15:26 +08:00
parent cfd8feb5eb
commit 01f5dcbb54
30 changed files with 431 additions and 22 deletions

View File

@@ -5,18 +5,20 @@ import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.helper.LoginHelper;
import com.ruoyi.xq.domain.UserVip;
import com.ruoyi.xq.domain.VipOrder;
import com.ruoyi.xq.domain.VipPrice;
import com.ruoyi.xq.dto.app.pay.OrderCreateVo;
import com.ruoyi.xq.dto.app.vip.GenVipOrderReq;
import com.ruoyi.xq.dto.app.vip.VipHomeVo;
import com.ruoyi.xq.enums.vip.UserVipOpenStatusEnum;
import com.ruoyi.xq.enums.vip.VipStatusEnum;
import com.ruoyi.xq.service.UserVipService;
import com.ruoyi.xq.service.VipOrderService;
import com.ruoyi.xq.service.VipPriceService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDateTime;
import java.util.List;
@@ -30,6 +32,8 @@ public class VipController {
private VipPriceService vipPriceService;
@Autowired
private UserVipService userVipService;
@Autowired
private VipOrderService vipOrderService;
@GetMapping("/home")
@Operation(summary = "获取VIP信息")
@@ -59,4 +63,16 @@ public class VipController {
}
return R.ok(vo);
}
@PostMapping("/order/create")
@Operation(summary = "生成VIP订单")
@Log(title = "生成VIP订单", businessType = BusinessType.OTHER, isSaveDb = false)
public R<OrderCreateVo> createVipOrder(@RequestBody GenVipOrderReq req){
Long userId = LoginHelper.getUserId();
VipOrder vipOrder = vipOrderService.createVipOrder(userId, req.getVipPriceSettingId());
OrderCreateVo result = new OrderCreateVo();
result.setPrice(vipOrder.getVipPrice());
result.setOrderNo(vipOrder.getOrderNo());
result.setOrderName(vipOrder.getOrderName());
return R.ok(result);
}
}

View File

@@ -2,20 +2,25 @@ package com.ruoyi.xq.controller.app;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.domain.PageQuery;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.helper.LoginHelper;
import com.ruoyi.common.utils.BeanConvertUtil;
import com.ruoyi.xq.domain.WithdrawSetting;
import com.ruoyi.xq.dto.app.common.IdReq;
import com.ruoyi.xq.dto.app.common.IdsReq;
import com.ruoyi.xq.dto.app.setting.WithdrawSettingVo;
import com.ruoyi.xq.dto.app.vip.VipHomeVo;
import com.ruoyi.xq.dto.app.withdraw.WithdrawListAppVo;
import com.ruoyi.xq.dto.app.withdraw.WithdrawListPageQuery;
import com.ruoyi.xq.service.UserWithdrawService;
import com.ruoyi.xq.service.WithdrawSettingService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@@ -26,6 +31,8 @@ public class WithdrawController {
@Autowired
private WithdrawSettingService withdrawSettingService;
@Autowired
private UserWithdrawService userWithdrawService;
@GetMapping("/setting")
@Operation(summary = "获取提现配置")
@@ -38,4 +45,23 @@ public class WithdrawController {
return R.ok(result);
}
@PostMapping("/save")
@Operation(summary = "申请提现")
@Log(title = "申请提现", businessType = BusinessType.OTHER, isSaveDb = false)
public R<Void> save(@RequestBody IdReq req){
userWithdrawService.saveWithdraw(LoginHelper.getUserId(),req.getId());
return R.ok();
}
@PostMapping("/logs/page")
@Operation(summary = "提现记录")
@Log(title = "申请记录", businessType = BusinessType.OTHER, isSaveDb = false)
public R<List<WithdrawListAppVo>> logsPage(PageQuery pageQuery, WithdrawListPageQuery query){
query.setUserId(LoginHelper.getUserId());
Page<WithdrawListAppVo> result = userWithdrawService.pageApp(pageQuery,query);
return R.ok(result.getRecords());
}
}

View File

@@ -36,7 +36,7 @@ public class UserWithdraw implements Serializable {
/**
* 跟踪ID
*/
private Long traceId;
private String traceId;
/**
* 订单号
*/

View File

@@ -53,6 +53,7 @@ public class VipOrder implements Serializable {
* 订单号
*/
private String orderNo;
private String orderName;
/**
* 平台
*/

View File

@@ -32,6 +32,7 @@ public class VipPrice implements Serializable {
*/
@Schema(description = "1-普通会员 2-黄金会员 3-钻石会员")
private Integer vipType;
private String vipName;
/**
* 1-月卡 2-季卡 3-年卡
*/

View File

@@ -0,0 +1,10 @@
package com.ruoyi.xq.dto.app.common;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
public class IdReq {
@Schema(description = "id")
private Long id;
}

View File

@@ -0,0 +1,16 @@
package com.ruoyi.xq.dto.app.pay;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
@Data
public class OrderCreateVo {
@Schema(description = "订单号")
private String orderNo;
@Schema(description = "支付金额")
private BigDecimal price;
@Schema(description = "订单名称")
private String orderName;
}

View File

@@ -0,0 +1,10 @@
package com.ruoyi.xq.dto.app.vip;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
public class GenVipOrderReq {
@Schema(description = "vip价格ID")
private Long vipPriceSettingId;
}

View File

@@ -0,0 +1,74 @@
package com.ruoyi.xq.dto.app.withdraw;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Data
public class WithdrawListAppVo {
@Schema(description = "Id")
private Long id;
/**
* 用户ID
*/
@Schema(description = "用户Id")
private Long userId;
/**
* 用户号
*/
@Schema(description = "用户号")
private String usercode;
/**
* 订单号
*/
@Schema(description = "订单号")
private String orderNo;
/**
* 提现金额
*/
@Schema(description = "提现金额")
private BigDecimal withdrawMoney;
/**
* 真实提现金额
*/
@Schema(description = "真实提现金额")
private BigDecimal realWithdrawMoney;
/**
* 提现手续费金额
*/
@Schema(description = "提现手续费金额")
private BigDecimal withdrawFees;
/**
* 提现手续费
*/
@Schema(description = "提现手续费")
private BigDecimal withdrawFeesCate;
/**
* 是否打款
*/
@Schema(description = "是否打款")
private Integer pay;
/**
* 1-待审核 2-审核成功 3-审核失败
*/
@Schema(description = "1-待审核 2-审核成功 3-审核失败")
private Integer auditStatus;
/**
* 审核备注
*/
@Schema(description = "审核备注")
private String auditRemark;
/**
* 审核时间
*/
@Schema(description = "审核时间")
private LocalDateTime auditTime;
@Schema(description = "申请时间")
private LocalDateTime createTime;
}

View File

@@ -0,0 +1,13 @@
package com.ruoyi.xq.dto.app.withdraw;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
public class WithdrawListPageQuery {
@Schema(hidden = true)
private Long userId;
@Schema(description = "1-待审核 2-审核成功 3-审核失败")
private Integer auditStatus;
}

View File

@@ -0,0 +1,16 @@
package com.ruoyi.xq.enums.common;
import lombok.Getter;
@Getter
public enum OrderTypeEnum {
VIP("V"),
WITHDRAW("W"),
;
private final String type;
OrderTypeEnum(String type) {
this.type = type;
}
}

View File

@@ -0,0 +1,16 @@
package com.ruoyi.xq.enums.common;
import lombok.Getter;
@Getter
public enum TraceIdEnum {
WITHDRAW("WT","提现"),
;
private final String code;
private final String text;
TraceIdEnum(String code, String text) {
this.code = code;
this.text = text;
}
}

View File

@@ -0,0 +1,20 @@
package com.ruoyi.xq.enums.pay;
import lombok.Getter;
@Getter
public enum PayStatusEnum {
READY_PAY(0,"待支付"),
PAY(1,"已支付"),
REFUND(5,"已退款"),
NO_PAY(10,"无需支付"),
;
private final Integer code;
private final String name;
PayStatusEnum(Integer code, String name) {
this.code = code;
this.name = name;
}
}

View File

@@ -0,0 +1,28 @@
package com.ruoyi.xq.manager;
import cn.hutool.core.lang.Snowflake;
import cn.hutool.core.util.IdUtil;
import com.ruoyi.xq.enums.common.OrderTypeEnum;
public class OrderNoUtil {
public static final Snowflake snowflake = IdUtil.getSnowflake(1, 1);
public static String gen(OrderTypeEnum sub){
return sub.getType() + snowflake.nextIdStr();
}
public static OrderTypeEnum getType(String orderNo){
if(orderNo == null){
return null;
}
OrderTypeEnum[] values = OrderTypeEnum.values();
for (OrderTypeEnum value : values) {
if(orderNo.startsWith(value.getType())){
return value;
}
}
return null;
}
}

View File

@@ -0,0 +1,25 @@
package com.ruoyi.xq.manager;
import cn.hutool.core.lang.Snowflake;
import cn.hutool.core.util.IdUtil;
import com.ruoyi.xq.enums.common.TraceIdEnum;
public class TraceIdManager {
private static final Snowflake SNOWFLAKE;
static {
int dataId = 1;
String id = System.getProperty("DATA_ID");
if(id != null){
dataId = Integer.parseInt(id);
}
SNOWFLAKE = IdUtil.getSnowflake(1, dataId);
}
public static String gen(TraceIdEnum traceIdEnum){
return traceIdEnum.getCode() + SNOWFLAKE.nextIdStr();
}
}

View File

@@ -2,6 +2,9 @@ package com.ruoyi.xq.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.xq.domain.UserExtend;
import org.apache.ibatis.annotations.Param;
import java.math.BigDecimal;
/**
* 用户邀请Mapper接口
@@ -11,4 +14,5 @@ import com.ruoyi.xq.domain.UserExtend;
*/
public interface UserExtendMapper extends BaseMapper<UserExtend> {
boolean decrIncome(@Param("userId") Long userId, @Param("price") BigDecimal price);
}

View File

@@ -1,7 +1,11 @@
package com.ruoyi.xq.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.xq.domain.UserWithdraw;
import com.ruoyi.xq.dto.app.withdraw.WithdrawListAppVo;
import com.ruoyi.xq.dto.app.withdraw.WithdrawListPageQuery;
import org.apache.ibatis.annotations.Param;
/**
* 用户提现审核Mapper接口
@@ -11,4 +15,5 @@ import com.ruoyi.xq.domain.UserWithdraw;
*/
public interface UserWithdrawMapper extends BaseMapper<UserWithdraw> {
Page<WithdrawListAppVo> pageApp(@Param("build") Page<Object> build, @Param("query") WithdrawListPageQuery query);
}

View File

@@ -1,7 +1,11 @@
package com.ruoyi.xq.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.xq.domain.WithdrawSetting;
import com.ruoyi.xq.dto.app.withdraw.WithdrawListAppVo;
import com.ruoyi.xq.dto.app.withdraw.WithdrawListPageQuery;
import org.apache.ibatis.annotations.Param;
/**
* 提现配置Mapper接口

View File

@@ -3,6 +3,8 @@ package com.ruoyi.xq.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.xq.domain.UserExtend;
import java.math.BigDecimal;
/**
* 用户邀请Service接口
*
@@ -11,4 +13,5 @@ import com.ruoyi.xq.domain.UserExtend;
*/
public interface UserExtendService extends IService<UserExtend> {
boolean withdraw(Long userId, BigDecimal withdrawPrice);
}

View File

@@ -2,6 +2,7 @@ package com.ruoyi.xq.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.xq.domain.UserVip;
import com.ruoyi.xq.domain.VipOrder;
/**
* VIP用户Service接口
@@ -16,4 +17,5 @@ public interface UserVipService extends IService<UserVip> {
UserVip getUserVipNormal(Long userId, Integer vipType);
UserVip getByUserVipMaster(Long userId);
}

View File

@@ -1,7 +1,11 @@
package com.ruoyi.xq.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.common.core.domain.PageQuery;
import com.ruoyi.xq.domain.UserWithdraw;
import com.ruoyi.xq.dto.app.withdraw.WithdrawListAppVo;
import com.ruoyi.xq.dto.app.withdraw.WithdrawListPageQuery;
/**
* 用户提现审核Service接口
@@ -11,4 +15,7 @@ import com.ruoyi.xq.domain.UserWithdraw;
*/
public interface UserWithdrawService extends IService<UserWithdraw> {
void saveWithdraw(Long userId, Long withdrawSettingId);
Page<WithdrawListAppVo> pageApp(PageQuery pageQuery, WithdrawListPageQuery query);
}

View File

@@ -10,4 +10,5 @@ import com.ruoyi.xq.domain.VipOrder;
* @date 2024-03-04
*/
public interface VipOrderService extends IService<VipOrder> {
VipOrder createVipOrder(Long userId, Long vipPriceSettingId);
}

View File

@@ -11,4 +11,5 @@ import com.ruoyi.xq.domain.WithdrawSetting;
*/
public interface WithdrawSettingService extends IService<WithdrawSetting> {
}

View File

@@ -7,6 +7,8 @@ import com.ruoyi.xq.service.UserExtendService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
/**
* 用户邀请Service业务层处理
*
@@ -17,4 +19,11 @@ import org.springframework.stereotype.Service;
@Service
public class UserExtendServiceImpl extends ServiceImpl<UserExtendMapper,UserExtend> implements UserExtendService {
@Override
public boolean withdraw(Long userId, BigDecimal withdrawPrice){
boolean exists = baseMapper.decrIncome(userId, withdrawPrice);
return exists;
}
}

View File

@@ -23,7 +23,6 @@ import java.util.List;
@Service
public class UserVipServiceImpl extends ServiceImpl<UserVipMapper,UserVip> implements UserVipService {
@Override
public UserVip getUserVip(Long userId, Integer vipType){
return this.getOne(Wrappers.lambdaQuery(UserVip.class)

View File

@@ -1,11 +1,30 @@
package com.ruoyi.xq.service.impl;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.common.core.domain.PageQuery;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.xq.domain.User;
import com.ruoyi.xq.domain.UserWithdraw;
import com.ruoyi.xq.domain.WithdrawSetting;
import com.ruoyi.xq.dto.app.withdraw.WithdrawListAppVo;
import com.ruoyi.xq.dto.app.withdraw.WithdrawListPageQuery;
import com.ruoyi.xq.enums.common.AuditEnum;
import com.ruoyi.xq.enums.common.OrderTypeEnum;
import com.ruoyi.xq.enums.common.TraceIdEnum;
import com.ruoyi.xq.manager.OrderNoUtil;
import com.ruoyi.xq.manager.TraceIdManager;
import com.ruoyi.xq.mapper.UserWithdrawMapper;
import com.ruoyi.xq.service.UserExtendService;
import com.ruoyi.xq.service.UserService;
import com.ruoyi.xq.service.UserWithdrawService;
import com.ruoyi.xq.service.WithdrawSettingService;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
/**
* 用户提现审核Service业务层处理
@@ -17,4 +36,42 @@ import org.springframework.stereotype.Service;
@Service
public class UserWithdrawServiceImpl extends ServiceImpl<UserWithdrawMapper,UserWithdraw> implements UserWithdrawService {
@Autowired
private WithdrawSettingService withdrawSettingService;
@Autowired
private UserExtendService userExtendService;
@Autowired
private UserService userService;
@Override
@Transactional(rollbackFor = Exception.class)
public void saveWithdraw(Long userId, Long withdrawSettingId) {
User user = userService.getById(userId);
WithdrawSetting withdrawSetting = withdrawSettingService.getById(withdrawSettingId);
if(withdrawSetting == null){
throw new ServiceException("提现参数错误");
}
boolean withdraw = userExtendService.withdraw(userId, withdrawSetting.getMoney());
if(!withdraw){
throw new ServiceException("提现失败,余额不足");
}
String traceId = TraceIdManager.gen(TraceIdEnum.WITHDRAW);
String orderNo = OrderNoUtil.gen(OrderTypeEnum.WITHDRAW);
UserWithdraw userWithdraw = new UserWithdraw();
userWithdraw.setUserId(user.getId());
userWithdraw.setUsercode(user.getUsercode());
userWithdraw.setTraceId(traceId);
userWithdraw.setOrderNo(orderNo);
userWithdraw.setWithdrawMoney(withdrawSetting.getMoney());
userWithdraw.setRealWithdrawMoney(withdrawSetting.getMoney());
userWithdraw.setWithdrawFees(BigDecimal.ZERO);
userWithdraw.setWithdrawFeesCate(BigDecimal.ZERO);
userWithdraw.setAuditStatus(AuditEnum.AUDITING.getCode());
this.save(userWithdraw);
}
@Override
public Page<WithdrawListAppVo> pageApp(PageQuery pageQuery, WithdrawListPageQuery query) {
return baseMapper.pageApp(pageQuery.build(), query);
}
}

View File

@@ -1,10 +1,19 @@
package com.ruoyi.xq.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.xq.domain.User;
import com.ruoyi.xq.domain.VipOrder;
import com.ruoyi.xq.domain.VipPrice;
import com.ruoyi.xq.enums.common.OrderTypeEnum;
import com.ruoyi.xq.enums.pay.PayStatusEnum;
import com.ruoyi.xq.manager.OrderNoUtil;
import com.ruoyi.xq.mapper.VipOrderMapper;
import com.ruoyi.xq.service.UserService;
import com.ruoyi.xq.service.VipOrderService;
import com.ruoyi.xq.service.VipPriceService;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
@@ -17,4 +26,29 @@ import org.springframework.stereotype.Service;
@Service
public class VipOrderServiceImpl extends ServiceImpl<VipOrderMapper,VipOrder> implements VipOrderService {
@Autowired
private VipPriceService vipPriceService;
@Autowired
private UserService userService;
@Override
public VipOrder createVipOrder(Long userId, Long vipPriceSettingId) {
VipPrice vipPrice = vipPriceService.getById(vipPriceSettingId);
if(vipPrice == null){
throw new ServiceException("vip价格错误");
}
String orderNo = OrderNoUtil.gen(OrderTypeEnum.VIP);
User user = userService.getById(userId);
VipOrder vipOrder = new VipOrder();
vipOrder.setUserId(user.getId());
vipOrder.setUsercode(user.getUsercode());
vipOrder.setVipId(vipPrice.getId());
vipOrder.setVipType(vipPrice.getVipType());
vipOrder.setVipTime(vipPrice.getVipTime());
vipOrder.setVipPrice(vipPrice.getVipPrice());
vipOrder.setOrderNo(orderNo);
vipOrder.setOrderName(vipPrice.getVipName());
vipOrder.setPayStatus(PayStatusEnum.READY_PAY.getCode());
this.save(vipOrder);
return vipOrder;
}
}

View File

@@ -1,11 +1,19 @@
package com.ruoyi.xq.service.impl;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.common.core.domain.PageQuery;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.xq.domain.WithdrawSetting;
import com.ruoyi.xq.dto.app.withdraw.WithdrawListAppVo;
import com.ruoyi.xq.dto.app.withdraw.WithdrawListPageQuery;
import com.ruoyi.xq.mapper.WithdrawSettingMapper;
import com.ruoyi.xq.service.UserExtendService;
import com.ruoyi.xq.service.WithdrawSettingService;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* 提现配置Service业务层处理

View File

@@ -4,19 +4,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.xq.mapper.UserExtendMapper">
<resultMap type="com.ruoyi.xq.domain.UserExtend" id="UserExtendResult">
<result property="id" column="id"/>
<result property="userId" column="user_id"/>
<result property="usercode" column="usercode"/>
<result property="incomeCoin" column="income_coin"/>
<result property="inviteId" column="invite_id"/>
<result property="inviteCode" column="invite_code"/>
<result property="consumeTotal" column="consume_total"/>
<result property="withdrawTotal" column="withdraw_total"/>
<result property="cashbackTotal" column="cashback_total"/>
<result property="createTime" column="create_time"/>
<result property="updateTime" column="update_time"/>
</resultMap>
<update id="decrIncome">
update xq_user_extend
set income_coin = income_coin - #{price}
where income_coin - #{price} > 0 and user_id = #{userId}
</update>
</mapper>

View File

@@ -25,6 +25,17 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<result property="updateTime" column="update_time"/>
<result property="deleteFlag" column="delete_flag"/>
</resultMap>
<select id="pageApp" resultType="com.ruoyi.xq.dto.app.withdraw.WithdrawListAppVo">
select
t1.id, t1.user_id, t1.usercode, t1.order_no,
t1.withdraw_money,t1.real_withdraw_money, t1.withdraw_fees,t1.withdraw_fees_cate,
t1.pay, t1.audit_status, t1.audit_remark, t1.audit_time, t1.create_time
from xq_user_withdraw t1
where t1.user_id = #{query.userId}
<if test="query.auditStatus != null">
and t1.audit_status = #{query.auditStatus}
</if>
</select>
</mapper>