Compare commits
2
Commits
4585c824b7
...
20ab909329
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
20ab909329 | ||
|
|
45b2c3073f |
@@ -0,0 +1,18 @@
|
||||
package org.jiayunet.admin.constant;
|
||||
|
||||
/**
|
||||
* B 端管理后台 Redis Key 常量
|
||||
*
|
||||
* @author zk
|
||||
*/
|
||||
public interface AdminRedisKeyName {
|
||||
|
||||
/** 管理员登录 token 前缀,完整 key: admin:login:token:{adminId} */
|
||||
String LOGIN_TOKEN = "login:token:";
|
||||
|
||||
/** 登录失败次数计数,完整 key: admin:login:fail:{username} */
|
||||
String LOGIN_FAIL_COUNT = "login:fail:";
|
||||
|
||||
/** 账号锁定标记,完整 key: admin:login:lock:{username} */
|
||||
String LOGIN_LOCK = "login:lock:";
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package org.jiayunet.admin.controller;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.jiayunet.admin.pojo.param.login.AdminLoginParam;
|
||||
import org.jiayunet.admin.service.AdminLoginService;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* 管理员登录控制类
|
||||
*
|
||||
* @author zk
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/public")
|
||||
@AllArgsConstructor
|
||||
@Validated
|
||||
public class AdminLoginController {
|
||||
|
||||
private AdminLoginService adminLoginService;
|
||||
|
||||
@PostMapping("/login")
|
||||
public Long login(@Validated @RequestBody AdminLoginParam param, HttpServletRequest request, HttpServletResponse response) {
|
||||
return adminLoginService.login(param.getUsername(), param.getPassword(), request, response);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package org.jiayunet.admin.controller;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.jiayunet.admin.pojo.dto.order.OrderListDto;
|
||||
import org.jiayunet.admin.pojo.dto.order.OrderStatsDto;
|
||||
import org.jiayunet.admin.pojo.param.order.OrderPageParam;
|
||||
import org.jiayunet.admin.service.AdminOrderService;
|
||||
import org.jiayunet.pojo.PageResult;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* 管理后台-订单管理控制类
|
||||
*
|
||||
* @author zk
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/order")
|
||||
@AllArgsConstructor
|
||||
@Validated
|
||||
public class AdminOrderController {
|
||||
|
||||
private AdminOrderService adminOrderService;
|
||||
|
||||
/** 订单分页列表 */
|
||||
@PostMapping("/list")
|
||||
public PageResult<OrderListDto> list(@Validated @RequestBody OrderPageParam param) {
|
||||
return adminOrderService.pageList(param);
|
||||
}
|
||||
|
||||
/** 订单统计 */
|
||||
@GetMapping("/stats")
|
||||
public OrderStatsDto stats() {
|
||||
return adminOrderService.stats();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package org.jiayunet.admin.controller;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.jiayunet.admin.pojo.param.product.ProductPageParam;
|
||||
import org.jiayunet.admin.pojo.param.product.ProductSaveParam;
|
||||
import org.jiayunet.admin.service.AdminProductService;
|
||||
import org.jiayunet.pojo.PageResult;
|
||||
import org.jiayunet.pojo.po.MemberProduct;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* 管理后台-商品管理控制类
|
||||
*
|
||||
* @author zk
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/product")
|
||||
@AllArgsConstructor
|
||||
@Validated
|
||||
public class AdminProductController {
|
||||
|
||||
private AdminProductService adminProductService;
|
||||
|
||||
/** 商品分页列表 */
|
||||
@PostMapping("/list")
|
||||
public PageResult<MemberProduct> list(@Validated @RequestBody ProductPageParam param) {
|
||||
return adminProductService.pageList(param);
|
||||
}
|
||||
|
||||
/** 新增/编辑商品 */
|
||||
@PostMapping("/save")
|
||||
public void save(@Validated @RequestBody ProductSaveParam param) {
|
||||
adminProductService.save(param);
|
||||
}
|
||||
|
||||
/** 上架/下架 */
|
||||
@PostMapping("/updateStatus")
|
||||
public void updateStatus(@RequestParam Long id, @RequestParam Integer status) {
|
||||
adminProductService.updateStatus(id, status);
|
||||
}
|
||||
|
||||
/** 删除商品 */
|
||||
@PostMapping("/delete")
|
||||
public void delete(@RequestParam Long id) {
|
||||
adminProductService.delete(id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package org.jiayunet.admin.controller;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.jiayunet.admin.pojo.dto.user.UserListDto;
|
||||
import org.jiayunet.admin.pojo.dto.user.UserStatsDto;
|
||||
import org.jiayunet.admin.pojo.param.user.UserPageParam;
|
||||
import org.jiayunet.admin.service.AdminUserService;
|
||||
import org.jiayunet.pojo.PageResult;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* 管理后台-用户管理控制类
|
||||
*
|
||||
* @author zk
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/user")
|
||||
@AllArgsConstructor
|
||||
@Validated
|
||||
public class AdminUserController {
|
||||
|
||||
private AdminUserService adminUserService;
|
||||
|
||||
/** 用户分页列表 */
|
||||
@PostMapping("/list")
|
||||
public PageResult<UserListDto> list(@Validated @RequestBody UserPageParam param) {
|
||||
return adminUserService.pageList(param);
|
||||
}
|
||||
|
||||
/** 用户统计(总数/今日/本周/本月) */
|
||||
@GetMapping("/stats")
|
||||
public UserStatsDto stats() {
|
||||
return adminUserService.stats();
|
||||
}
|
||||
|
||||
/** 禁用/启用用户 */
|
||||
@PostMapping("/updateStatus")
|
||||
public void updateStatus(@RequestParam Long userId, @RequestParam Integer status) {
|
||||
adminUserService.updateStatus(userId, status);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package org.jiayunet.admin.pojo.dto.order;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
/**
|
||||
* 订单列表出参
|
||||
*
|
||||
* @author zk
|
||||
*/
|
||||
@Data
|
||||
public class OrderListDto {
|
||||
|
||||
/** 订单ID */
|
||||
private Long id;
|
||||
/** 订单编号 */
|
||||
private String orderNo;
|
||||
/** 用户昵称 */
|
||||
private String userNick;
|
||||
/** 用户手机号 */
|
||||
private String mobileNumber;
|
||||
/** 商品名称 */
|
||||
private String productName;
|
||||
/** 实付金额(分) */
|
||||
private Integer payAmount;
|
||||
/** 支付渠道 1=微信 2=支付宝 */
|
||||
private Integer payChannel;
|
||||
/** 订单状态 0=待支付 1=已支付 2=已退款 3=已关闭 */
|
||||
private Integer status;
|
||||
/** 下单时间 */
|
||||
private Instant createTime;
|
||||
/** 支付时间 */
|
||||
private Instant payTime;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package org.jiayunet.admin.pojo.dto.order;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 订单统计出参
|
||||
*
|
||||
* @author zk
|
||||
*/
|
||||
@Data
|
||||
public class OrderStatsDto {
|
||||
|
||||
/** 总订单数 */
|
||||
private Long totalCount;
|
||||
/** 已支付订单数 */
|
||||
private Long paidCount;
|
||||
/** 今日已支付 */
|
||||
private Long todayPaid;
|
||||
/** 本周已支付 */
|
||||
private Long weekPaid;
|
||||
/** 本月已支付 */
|
||||
private Long monthPaid;
|
||||
/** 今日收入(分) */
|
||||
private Long todayIncome;
|
||||
/** 本周收入(分) */
|
||||
private Long weekIncome;
|
||||
/** 本月收入(分) */
|
||||
private Long monthIncome;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package org.jiayunet.admin.pojo.dto.user;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
/**
|
||||
* 用户列表出参
|
||||
*
|
||||
* @author zk
|
||||
*/
|
||||
@Data
|
||||
public class UserListDto {
|
||||
|
||||
/** 用户ID */
|
||||
private Long id;
|
||||
/** 昵称 */
|
||||
private String nick;
|
||||
/** 真实姓名 */
|
||||
private String realName;
|
||||
/** 手机号 */
|
||||
private String mobileNumber;
|
||||
/** 状态 0=正常 1=禁用 */
|
||||
private Integer status;
|
||||
/** 简历数量 */
|
||||
private Integer resumeCount;
|
||||
/** 注册时间 */
|
||||
private Instant createTime;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package org.jiayunet.admin.pojo.dto.user;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 用户统计出参
|
||||
*
|
||||
* @author zk
|
||||
*/
|
||||
@Data
|
||||
public class UserStatsDto {
|
||||
|
||||
/** 用户总数 */
|
||||
private Long total;
|
||||
/** 今日新增 */
|
||||
private Long today;
|
||||
/** 本周新增 */
|
||||
private Long thisWeek;
|
||||
/** 本月新增 */
|
||||
private Long thisMonth;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package org.jiayunet.admin.pojo.param.login;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.Size;
|
||||
|
||||
/**
|
||||
* 管理员登录入参
|
||||
*
|
||||
* @author zk
|
||||
*/
|
||||
@Data
|
||||
public class AdminLoginParam {
|
||||
|
||||
@NotBlank(message = "账号不能为空")
|
||||
@Size(max = 32, message = "账号长度不能超过32位")
|
||||
private String username;
|
||||
|
||||
@NotBlank(message = "密码不能为空")
|
||||
@Size(min = 6, max = 32, message = "密码长度6-32位")
|
||||
private String password;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package org.jiayunet.admin.pojo.param.order;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.jiayunet.pojo.PageParam;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
/**
|
||||
* 订单分页查询入参
|
||||
*
|
||||
* @author zk
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class OrderPageParam extends PageParam {
|
||||
|
||||
/** 订单状态 0=待支付 1=已支付 2=已退款 3=已关闭 */
|
||||
private Integer status;
|
||||
/** 支付渠道 1=微信 2=支付宝 */
|
||||
private Integer payChannel;
|
||||
/** 用户手机号(模糊) */
|
||||
private String mobileNumber;
|
||||
/** 下单时间-起 */
|
||||
private Instant startTime;
|
||||
/** 下单时间-止 */
|
||||
private Instant endTime;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package org.jiayunet.admin.pojo.param.product;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.jiayunet.pojo.PageParam;
|
||||
|
||||
/**
|
||||
* 商品分页查询入参
|
||||
*
|
||||
* @author zk
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class ProductPageParam extends PageParam {
|
||||
|
||||
/** 状态筛选 0=下架 1=上架 */
|
||||
private Integer status;
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package org.jiayunet.admin.pojo.param.product;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.Min;
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
/**
|
||||
* 商品新增/编辑入参
|
||||
*
|
||||
* @author zk
|
||||
*/
|
||||
@Data
|
||||
public class ProductSaveParam {
|
||||
|
||||
/** 商品ID(编辑时必传,新增时不传) */
|
||||
private Long id;
|
||||
|
||||
@NotBlank(message = "商品名称不能为空")
|
||||
private String productName;
|
||||
|
||||
/** 标签,如"限时优惠" */
|
||||
private String tag;
|
||||
|
||||
/** 主推标识 0=否 1=是 */
|
||||
private Integer isFeatured;
|
||||
|
||||
/** 购买按钮文字 */
|
||||
private String buyButtonText;
|
||||
|
||||
@NotNull(message = "价格不能为空")
|
||||
@Min(value = 1, message = "价格必须大于0")
|
||||
private Integer price;
|
||||
|
||||
/** 划线价(分) */
|
||||
private Integer originalPrice;
|
||||
|
||||
/** 折算月价(分) */
|
||||
private Integer monthlyPrice;
|
||||
|
||||
@NotNull(message = "有效天数不能为空")
|
||||
@Min(value = 1, message = "有效天数必须大于0")
|
||||
private Integer durationDays;
|
||||
|
||||
/** 排序 */
|
||||
private Integer sortOrder;
|
||||
|
||||
/** 状态 0=下架 1=上架 */
|
||||
private Integer status;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package org.jiayunet.admin.pojo.param.user;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.jiayunet.pojo.PageParam;
|
||||
|
||||
/**
|
||||
* 用户分页查询入参
|
||||
*
|
||||
* @author zk
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class UserPageParam extends PageParam {
|
||||
|
||||
/** 手机号(模糊搜索) */
|
||||
private String mobileNumber;
|
||||
|
||||
/** 状态筛选 0=正常 1=禁用 */
|
||||
private Integer status;
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
package org.jiayunet.admin.service;
|
||||
|
||||
import com.auth0.jwt.JWT;
|
||||
import com.auth0.jwt.algorithms.Algorithm;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jiayunet.admin.constant.AdminRedisKeyName;
|
||||
import org.jiayunet.exception.BusinessException;
|
||||
import org.jiayunet.exception.BusinessExpCodeEnum;
|
||||
import org.jiayunet.mapper.AdminUserMapper;
|
||||
import org.jiayunet.pojo.login.RedisLoginTokenInfo;
|
||||
import org.jiayunet.pojo.po.AdminUser;
|
||||
import org.jiayunet.tool.HttpIpTool;
|
||||
import org.jiayunet.tool.server.RedisServerTool;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 管理员登录服务
|
||||
* <p>依赖:AdminUserMapper(管理员查询)、RedisServerTool(token管理与登录失败计数)</p>
|
||||
* <p>使用表:bg_admin_user(查询管理员账号)</p>
|
||||
* <p>使用Redis:login:token:{adminId}(登录令牌)、login:fail:{username}(失败计数)、login:lock:{username}(账号锁定)</p>
|
||||
*
|
||||
* @author zk
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
public class AdminLoginService {
|
||||
|
||||
/** 密码错误允许最大次数 */
|
||||
private static final int MAX_FAIL_COUNT = 3;
|
||||
/** 账号锁定时长(小时) */
|
||||
private static final int LOCK_HOURS = 24;
|
||||
|
||||
@Value("${app.secret.token:youweiqingnian123}")
|
||||
private String secret;
|
||||
@Value("${app.login.token.exceed_time:43200}")
|
||||
private int tokenExceedTime;
|
||||
@Value("${app.login.device_online_quantity:5}")
|
||||
private int deviceOnlineQuantity;
|
||||
@Autowired
|
||||
private AdminUserMapper adminUserMapper;
|
||||
@Autowired
|
||||
private RedisServerTool redisServerTool;
|
||||
|
||||
/**
|
||||
* 账号密码登录
|
||||
* <p>1. 校验账号锁定状态 2. 查询管理员 3. 校验密码(失败累计,3次锁定24小时) 4. 生成JWT 5. 写入Redis管理多设备 6. 设置Cookie</p>
|
||||
*/
|
||||
public Long login(String username, String password, HttpServletRequest request, HttpServletResponse response) {
|
||||
Assert.hasText(username, "账号不能为空");
|
||||
Assert.hasText(password, "密码不能为空");
|
||||
|
||||
// 检查账号是否被锁定
|
||||
Assert.isTrue(!redisServerTool.hasKey(AdminRedisKeyName.LOGIN_LOCK + username), "账号已被锁定,请24小时后再试");
|
||||
|
||||
// 查询管理员
|
||||
AdminUser admin = adminUserMapper.selectOne(new LambdaQueryWrapper<AdminUser>().eq(AdminUser::getUsername, username).eq(AdminUser::getStatus, 0));
|
||||
Assert.notNull(admin, "账号或密码错误");
|
||||
|
||||
// 校验密码:MD5(明文 + salt)
|
||||
if (!md5(password + secret).equals(admin.getPassword())) {
|
||||
handleLoginFail(username);
|
||||
throw new BusinessException(BusinessExpCodeEnum.UNKNOWN_ERROR, "账号或密码错误");
|
||||
}
|
||||
|
||||
// 登录成功,清除失败计数
|
||||
clearLoginFail(username);
|
||||
|
||||
// 生成JWT
|
||||
String uuId = UUID.randomUUID().toString().replace("-", "");
|
||||
String token = JWT.create().withClaim("userId", admin.getId()).withClaim("uuId", uuId).sign(Algorithm.HMAC256(secret));
|
||||
|
||||
// 构建Redis登录信息
|
||||
String redisKey = AdminRedisKeyName.LOGIN_TOKEN + admin.getId();
|
||||
RedisLoginTokenInfo info = redisServerTool.get(redisKey, RedisLoginTokenInfo.class);
|
||||
if (info == null) {
|
||||
info = new RedisLoginTokenInfo();
|
||||
info.setUserId(admin.getId());
|
||||
info.setAuthority(new ArrayList<>());
|
||||
info.setRole(new ArrayList<>());
|
||||
}
|
||||
|
||||
// 过滤过期设备
|
||||
List<RedisLoginTokenInfo.LoginDevice> devices = info.getLoginDevices() == null ? new ArrayList<>() : info.getLoginDevices();
|
||||
long expireMillis = System.currentTimeMillis() - tokenExceedTime * 1000L;
|
||||
devices = devices.stream().filter(d -> d.getLastLoginTime().isAfter(Instant.ofEpochMilli(expireMillis))).collect(Collectors.toList());
|
||||
|
||||
// 超过设备上限,移除最早的
|
||||
while (devices.size() >= deviceOnlineQuantity) {
|
||||
devices.stream().min(Comparator.comparing(RedisLoginTokenInfo.LoginDevice::getLastLoginTime)).ifPresent(devices::remove);
|
||||
}
|
||||
|
||||
// 添加当前设备
|
||||
RedisLoginTokenInfo.LoginDevice device = new RedisLoginTokenInfo.LoginDevice();
|
||||
device.setUuId(uuId);
|
||||
device.setLastLoginTime(Instant.now());
|
||||
device.setLoginIp(HttpIpTool.gteRealIP(request));
|
||||
devices.add(device);
|
||||
|
||||
info.setLoginDevices(devices);
|
||||
redisServerTool.set(redisKey, info, tokenExceedTime, TimeUnit.SECONDS);
|
||||
|
||||
// 设置Cookie
|
||||
Cookie cookie = new Cookie("Token", token);
|
||||
cookie.setHttpOnly(true);
|
||||
cookie.setPath("/");
|
||||
cookie.setMaxAge(tokenExceedTime);
|
||||
response.addCookie(cookie);
|
||||
|
||||
return admin.getId();
|
||||
}
|
||||
|
||||
/** 处理登录失败:累计失败次数,达到上限则锁定账号24小时 */
|
||||
private void handleLoginFail(String username) {
|
||||
String failKey = AdminRedisKeyName.LOGIN_FAIL_COUNT + username;
|
||||
Integer failCount = redisServerTool.get(failKey, Integer.class);
|
||||
int currentCount = (failCount == null ? 0 : failCount) + 1;
|
||||
if (currentCount >= MAX_FAIL_COUNT) {
|
||||
redisServerTool.set(AdminRedisKeyName.LOGIN_LOCK + username, true, LOCK_HOURS, TimeUnit.HOURS);
|
||||
redisServerTool.delete(failKey);
|
||||
log.warn("管理员账号 [{}] 密码错误{}次,已锁定{}小时", username, MAX_FAIL_COUNT, LOCK_HOURS);
|
||||
} else {
|
||||
redisServerTool.set(failKey, currentCount, LOCK_HOURS, TimeUnit.HOURS);
|
||||
}
|
||||
}
|
||||
|
||||
/** 登录成功后清除失败计数 */
|
||||
private void clearLoginFail(String username) {
|
||||
redisServerTool.delete(AdminRedisKeyName.LOGIN_FAIL_COUNT + username);
|
||||
}
|
||||
|
||||
/** MD5 加密 */
|
||||
private String md5(String input) {
|
||||
try {
|
||||
java.security.MessageDigest md = java.security.MessageDigest.getInstance("MD5");
|
||||
byte[] digest = md.digest(input.getBytes(java.nio.charset.StandardCharsets.UTF_8));
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (byte b : digest) { sb.append(String.format("%02x", b)); }
|
||||
return sb.toString();
|
||||
} catch (java.security.NoSuchAlgorithmException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
package org.jiayunet.admin.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jiayunet.admin.pojo.dto.order.OrderListDto;
|
||||
import org.jiayunet.admin.pojo.dto.order.OrderStatsDto;
|
||||
import org.jiayunet.admin.pojo.param.order.OrderPageParam;
|
||||
import org.jiayunet.mapper.MemberOrderMapper;
|
||||
import org.jiayunet.mapper.MemberProductMapper;
|
||||
import org.jiayunet.mapper.UserMapper;
|
||||
import org.jiayunet.pojo.PageResult;
|
||||
import org.jiayunet.pojo.po.MemberOrder;
|
||||
import org.jiayunet.pojo.po.MemberProduct;
|
||||
import org.jiayunet.pojo.po.User;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.time.*;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 管理后台-订单管理服务
|
||||
* <p>依赖:MemberOrderMapper、UserMapper、MemberProductMapper</p>
|
||||
* <p>使用表:bg_member_order(订单列表/统计)、bg_user(关联用户信息)、bg_member_product(关联商品名)</p>
|
||||
*
|
||||
* @author zk
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
public class AdminOrderService {
|
||||
|
||||
@Autowired
|
||||
private MemberOrderMapper memberOrderMapper;
|
||||
@Autowired
|
||||
private UserMapper userMapper;
|
||||
@Autowired
|
||||
private MemberProductMapper memberProductMapper;
|
||||
|
||||
/**
|
||||
* 订单分页列表
|
||||
* <p>1. 如果按手机号筛选,先查用户ID集合 2. 分页查订单 3. 批量关联用户和商品信息 4. 组装DTO</p>
|
||||
*/
|
||||
public PageResult<OrderListDto> pageList(OrderPageParam param) {
|
||||
// 如果传了手机号,先查对应用户ID
|
||||
Set<Long> userIdFilter = null;
|
||||
if (StringUtils.hasText(param.getMobileNumber())) {
|
||||
List<User> users = userMapper.selectList(new LambdaQueryWrapper<User>().like(User::getMobileNumber, param.getMobileNumber()).select(User::getId));
|
||||
userIdFilter = users.stream().map(User::getId).collect(Collectors.toSet());
|
||||
if (userIdFilter.isEmpty()) return new PageResult<>(param.getPageNum().longValue(), param.getPageSize().longValue(), 0L, List.of());
|
||||
}
|
||||
|
||||
LambdaQueryWrapper<MemberOrder> wrapper = new LambdaQueryWrapper<MemberOrder>()
|
||||
.eq(param.getStatus() != null, MemberOrder::getStatus, param.getStatus())
|
||||
.eq(param.getPayChannel() != null, MemberOrder::getPayChannel, param.getPayChannel())
|
||||
.ge(param.getStartTime() != null, MemberOrder::getCreateTime, param.getStartTime())
|
||||
.le(param.getEndTime() != null, MemberOrder::getCreateTime, param.getEndTime())
|
||||
.in(userIdFilter != null, MemberOrder::getUserId, userIdFilter)
|
||||
.orderByDesc(MemberOrder::getCreateTime);
|
||||
|
||||
Page<MemberOrder> page = memberOrderMapper.selectPage(param.toPage(), wrapper);
|
||||
if (page.getRecords().isEmpty()) return PageResult.from((Page<OrderListDto>) page.convert(o -> new OrderListDto()));
|
||||
|
||||
// 批量查用户和商品
|
||||
Set<Long> userIds = page.getRecords().stream().map(MemberOrder::getUserId).collect(Collectors.toSet());
|
||||
Set<Long> productIds = page.getRecords().stream().map(MemberOrder::getProductId).collect(Collectors.toSet());
|
||||
Map<Long, User> userMap = userMapper.selectBatchIds(userIds).stream().collect(Collectors.toMap(User::getId, u -> u));
|
||||
Map<Long, MemberProduct> productMap = memberProductMapper.selectBatchIds(productIds).stream().collect(Collectors.toMap(MemberProduct::getId, p -> p));
|
||||
|
||||
Page<OrderListDto> dtoPage = (Page<OrderListDto>) page.convert(order -> {
|
||||
OrderListDto dto = new OrderListDto();
|
||||
dto.setId(order.getId());
|
||||
dto.setOrderNo(order.getOrderNo());
|
||||
dto.setPayAmount(order.getPayAmount());
|
||||
dto.setPayChannel(order.getPayChannel());
|
||||
dto.setStatus(order.getStatus());
|
||||
dto.setCreateTime(order.getCreateTime());
|
||||
dto.setPayTime(order.getPayTime());
|
||||
User user = userMap.get(order.getUserId());
|
||||
if (user != null) { dto.setUserNick(user.getNick()); dto.setMobileNumber(user.getMobileNumber()); }
|
||||
MemberProduct product = productMap.get(order.getProductId());
|
||||
if (product != null) { dto.setProductName(product.getProductName()); }
|
||||
return dto;
|
||||
});
|
||||
return PageResult.from(dtoPage);
|
||||
}
|
||||
|
||||
/**
|
||||
* 订单统计:总数/已支付数/今日本周本月已支付数和收入
|
||||
*/
|
||||
public OrderStatsDto stats() {
|
||||
OrderStatsDto dto = new OrderStatsDto();
|
||||
ZoneId zone = ZoneId.of("Asia/Shanghai");
|
||||
LocalDate now = LocalDate.now(zone);
|
||||
Instant todayStart = now.atStartOfDay(zone).toInstant();
|
||||
Instant weekStart = now.with(DayOfWeek.MONDAY).atStartOfDay(zone).toInstant();
|
||||
Instant monthStart = now.withDayOfMonth(1).atStartOfDay(zone).toInstant();
|
||||
|
||||
dto.setTotalCount(memberOrderMapper.selectCount(new LambdaQueryWrapper<MemberOrder>()));
|
||||
dto.setPaidCount(memberOrderMapper.selectCount(new LambdaQueryWrapper<MemberOrder>().eq(MemberOrder::getStatus, 1)));
|
||||
|
||||
// 今日/本周/本月已支付订单
|
||||
List<MemberOrder> todayOrders = memberOrderMapper.selectList(new LambdaQueryWrapper<MemberOrder>().eq(MemberOrder::getStatus, 1).ge(MemberOrder::getPayTime, todayStart));
|
||||
List<MemberOrder> weekOrders = memberOrderMapper.selectList(new LambdaQueryWrapper<MemberOrder>().eq(MemberOrder::getStatus, 1).ge(MemberOrder::getPayTime, weekStart));
|
||||
List<MemberOrder> monthOrders = memberOrderMapper.selectList(new LambdaQueryWrapper<MemberOrder>().eq(MemberOrder::getStatus, 1).ge(MemberOrder::getPayTime, monthStart));
|
||||
|
||||
dto.setTodayPaid((long) todayOrders.size());
|
||||
dto.setWeekPaid((long) weekOrders.size());
|
||||
dto.setMonthPaid((long) monthOrders.size());
|
||||
dto.setTodayIncome(todayOrders.stream().mapToLong(o -> o.getPayAmount() == null ? 0 : o.getPayAmount()).sum());
|
||||
dto.setWeekIncome(weekOrders.stream().mapToLong(o -> o.getPayAmount() == null ? 0 : o.getPayAmount()).sum());
|
||||
dto.setMonthIncome(monthOrders.stream().mapToLong(o -> o.getPayAmount() == null ? 0 : o.getPayAmount()).sum());
|
||||
return dto;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package org.jiayunet.admin.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jiayunet.admin.pojo.param.product.ProductPageParam;
|
||||
import org.jiayunet.admin.pojo.param.product.ProductSaveParam;
|
||||
import org.jiayunet.mapper.MemberProductMapper;
|
||||
import org.jiayunet.pojo.PageResult;
|
||||
import org.jiayunet.pojo.po.MemberProduct;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* 管理后台-商品管理服务
|
||||
* <p>依赖:MemberProductMapper</p>
|
||||
* <p>使用表:bg_member_product(CRUD)</p>
|
||||
*
|
||||
* @author zk
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
public class AdminProductService {
|
||||
|
||||
@Autowired
|
||||
private MemberProductMapper memberProductMapper;
|
||||
|
||||
/** 商品分页列表 */
|
||||
public PageResult<MemberProduct> pageList(ProductPageParam param) {
|
||||
LambdaQueryWrapper<MemberProduct> wrapper = new LambdaQueryWrapper<MemberProduct>()
|
||||
.eq(param.getStatus() != null, MemberProduct::getStatus, param.getStatus())
|
||||
.orderByAsc(MemberProduct::getSortOrder);
|
||||
Page<MemberProduct> page = memberProductMapper.selectPage(param.toPage(), wrapper);
|
||||
return PageResult.from(page);
|
||||
}
|
||||
|
||||
/** 新增/编辑商品 */
|
||||
public void save(ProductSaveParam param) {
|
||||
MemberProduct product = new MemberProduct();
|
||||
BeanUtils.copyProperties(param, product);
|
||||
if (param.getId() == null) {
|
||||
if (product.getStatus() == null) product.setStatus(0);
|
||||
if (product.getSortOrder() == null) product.setSortOrder(0);
|
||||
if (product.getIsFeatured() == null) product.setIsFeatured(0);
|
||||
memberProductMapper.insert(product);
|
||||
} else {
|
||||
product.setId(param.getId());
|
||||
memberProductMapper.updateById(product);
|
||||
}
|
||||
}
|
||||
|
||||
/** 上架/下架商品 */
|
||||
public void updateStatus(Long id, Integer status) {
|
||||
Assert.notNull(id, "商品ID不能为空");
|
||||
Assert.isTrue(status == 0 || status == 1, "状态值非法");
|
||||
memberProductMapper.update(null, new LambdaUpdateWrapper<MemberProduct>().eq(MemberProduct::getId, id).set(MemberProduct::getStatus, status));
|
||||
}
|
||||
|
||||
/** 删除商品(逻辑删除) */
|
||||
public void delete(Long id) {
|
||||
Assert.notNull(id, "商品ID不能为空");
|
||||
memberProductMapper.deleteById(id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
package org.jiayunet.admin.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jiayunet.admin.pojo.dto.user.UserListDto;
|
||||
import org.jiayunet.admin.pojo.dto.user.UserStatsDto;
|
||||
import org.jiayunet.admin.pojo.param.user.UserPageParam;
|
||||
import org.jiayunet.mapper.UserMapper;
|
||||
import org.jiayunet.mapper.UserResumeMapper;
|
||||
import org.jiayunet.pojo.PageResult;
|
||||
import org.jiayunet.pojo.po.User;
|
||||
import org.jiayunet.pojo.po.UserResume;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.time.*;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 管理后台-用户管理服务
|
||||
* <p>依赖:UserMapper(用户查询/更新)、UserResumeMapper(简历数量统计)</p>
|
||||
* <p>使用表:bg_user(用户列表/统计/禁用)、bg_user_resume(简历数量)</p>
|
||||
*
|
||||
* @author zk
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
public class AdminUserService {
|
||||
|
||||
@Autowired
|
||||
private UserMapper userMapper;
|
||||
@Autowired
|
||||
private UserResumeMapper userResumeMapper;
|
||||
|
||||
/**
|
||||
* 用户分页列表
|
||||
* <p>1. 分页查询用户 2. 批量查询每个用户的简历数量 3. 组装返回</p>
|
||||
*/
|
||||
public PageResult<UserListDto> pageList(UserPageParam param) {
|
||||
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>()
|
||||
.like(StringUtils.hasText(param.getMobileNumber()), User::getMobileNumber, param.getMobileNumber())
|
||||
.like(StringUtils.hasText(param.getKeyword()), User::getNick, param.getKeyword())
|
||||
.eq(param.getStatus() != null, User::getStatus, param.getStatus())
|
||||
.orderByDesc(User::getCreateTime);
|
||||
|
||||
Page<User> page = userMapper.selectPage(param.toPage(), wrapper);
|
||||
if (page.getRecords().isEmpty()) return PageResult.from((Page<UserListDto>) page.convert(u -> new UserListDto()));
|
||||
|
||||
// 批量查询简历数量
|
||||
List<Long> userIds = page.getRecords().stream().map(User::getId).collect(Collectors.toList());
|
||||
List<UserResume> resumes = userResumeMapper.selectList(new LambdaQueryWrapper<UserResume>().in(UserResume::getUserId, userIds).select(UserResume::getUserId));
|
||||
Map<Long, Long> resumeCountMap = resumes.stream().collect(Collectors.groupingBy(UserResume::getUserId, Collectors.counting()));
|
||||
|
||||
// 组装DTO
|
||||
Page<UserListDto> dtoPage = (Page<UserListDto>) page.convert(user -> {
|
||||
UserListDto dto = new UserListDto();
|
||||
dto.setId(user.getId());
|
||||
dto.setNick(user.getNick());
|
||||
dto.setRealName(user.getRealName());
|
||||
dto.setMobileNumber(user.getMobileNumber());
|
||||
dto.setStatus(user.getStatus());
|
||||
dto.setResumeCount(resumeCountMap.getOrDefault(user.getId(), 0L).intValue());
|
||||
dto.setCreateTime(user.getCreateTime());
|
||||
return dto;
|
||||
});
|
||||
return PageResult.from(dtoPage);
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户统计:总数 / 今日 / 本周 / 本月 新增
|
||||
*/
|
||||
public UserStatsDto stats() {
|
||||
UserStatsDto dto = new UserStatsDto();
|
||||
ZoneId zone = ZoneId.of("Asia/Shanghai");
|
||||
LocalDate now = LocalDate.now(zone);
|
||||
|
||||
Instant todayStart = now.atStartOfDay(zone).toInstant();
|
||||
Instant weekStart = now.with(DayOfWeek.MONDAY).atStartOfDay(zone).toInstant();
|
||||
Instant monthStart = now.withDayOfMonth(1).atStartOfDay(zone).toInstant();
|
||||
|
||||
dto.setTotal(userMapper.selectCount(new LambdaQueryWrapper<User>()));
|
||||
dto.setToday(userMapper.selectCount(new LambdaQueryWrapper<User>().ge(User::getCreateTime, todayStart)));
|
||||
dto.setThisWeek(userMapper.selectCount(new LambdaQueryWrapper<User>().ge(User::getCreateTime, weekStart)));
|
||||
dto.setThisMonth(userMapper.selectCount(new LambdaQueryWrapper<User>().ge(User::getCreateTime, monthStart)));
|
||||
return dto;
|
||||
}
|
||||
|
||||
/**
|
||||
* 禁用/启用用户
|
||||
*/
|
||||
public void updateStatus(Long userId, Integer status) {
|
||||
Assert.notNull(userId, "用户ID不能为空");
|
||||
Assert.isTrue(status == 0 || status == 1, "状态值非法");
|
||||
userMapper.update(null, new LambdaUpdateWrapper<User>().eq(User::getId, userId).set(User::getStatus, status));
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 9.2 KiB |
@@ -0,0 +1,9 @@
|
||||
/**
|
||||
* 认证相关 API
|
||||
*/
|
||||
const authApi = {
|
||||
/** 登录 */
|
||||
login(username, password) {
|
||||
return request.post('/public/login', { username, password });
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* 统一请求封装
|
||||
*/
|
||||
const request = (() => {
|
||||
const BASE_URL = '/admin';
|
||||
|
||||
/** 从 cookie 中获取 Token */
|
||||
function getToken() {
|
||||
const match = document.cookie.match(/(?:^|;\s*)Token=([^;]*)/);
|
||||
return match ? match[1] : '';
|
||||
}
|
||||
|
||||
/** 统一请求方法 */
|
||||
async function http(url, options = {}) {
|
||||
const config = {
|
||||
headers: { 'Content-Type': 'application/json', 'Token': getToken(), ...options.headers },
|
||||
...options
|
||||
};
|
||||
const response = await fetch(BASE_URL + url, config);
|
||||
|
||||
// 401 未登录,跳转登录页
|
||||
if (response.status === 401) {
|
||||
window.location.href = BASE_URL + '/page/login';
|
||||
return Promise.reject('未登录');
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
// 业务异常
|
||||
if (result.status !== 200) {
|
||||
const msg = result.message || '请求失败';
|
||||
alert(msg);
|
||||
return Promise.reject(msg);
|
||||
}
|
||||
|
||||
return result.data;
|
||||
}
|
||||
|
||||
return {
|
||||
get(url, params) {
|
||||
const query = params ? '?' + new URLSearchParams(params).toString() : '';
|
||||
return http(url + query, { method: 'GET' });
|
||||
},
|
||||
post(url, data) {
|
||||
return http(url, { method: 'POST', body: JSON.stringify(data) });
|
||||
},
|
||||
put(url, data) {
|
||||
return http(url, { method: 'PUT', body: JSON.stringify(data) });
|
||||
},
|
||||
del(url, data) {
|
||||
return http(url, { method: 'DELETE', body: data ? JSON.stringify(data) : undefined });
|
||||
}
|
||||
};
|
||||
})();
|
||||
@@ -3,10 +3,121 @@
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<title>OfferPie 管理后台 - 登录</title>
|
||||
<title>OfferPie 管理后台</title>
|
||||
<link rel="stylesheet" href="https://unpkg.com/element-plus/dist/index.css"/>
|
||||
<style>
|
||||
body { margin: 0; padding: 0; }
|
||||
#app {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
background: #f5f7fa;
|
||||
}
|
||||
.left-panel {
|
||||
flex: 1;
|
||||
background: linear-gradient(160deg, #1d1e3c 0%, #2b2d5e 100%);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
padding: 0 80px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
.left-panel::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 400px;
|
||||
height: 400px;
|
||||
border-radius: 50%;
|
||||
background: rgba(64, 158, 255, 0.06);
|
||||
top: 10%;
|
||||
right: -100px;
|
||||
}
|
||||
.left-panel .logo { display: flex; align-items: center; gap: 14px; margin-bottom: 24px; }
|
||||
.left-panel .logo img { height: 44px; }
|
||||
.left-panel .logo span { font-size: 26px; font-weight: 600; color: #fff; }
|
||||
.left-panel .desc { font-size: 15px; color: rgba(255,255,255,0.5); line-height: 1.8; }
|
||||
.left-panel .footer { position: absolute; bottom: 30px; left: 80px; font-size: 12px; color: rgba(255,255,255,0.2); }
|
||||
.right-panel {
|
||||
width: 480px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 40px;
|
||||
}
|
||||
.login-box { width: 100%; max-width: 340px; }
|
||||
.login-box h2 { font-size: 22px; color: #303133; margin-bottom: 6px; }
|
||||
.login-box .sub { font-size: 13px; color: #909399; margin-bottom: 32px; }
|
||||
.login-box .el-button { width: 100%; margin-top: 8px; }
|
||||
@media (max-width: 900px) {
|
||||
.left-panel { display: none; }
|
||||
.right-panel { width: 100%; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>OfferPie 管理后台</h1>
|
||||
<p>登录页面占位 - 待实现</p>
|
||||
<div id="app">
|
||||
<div class="left-panel">
|
||||
<div class="logo">
|
||||
<img src="/admin/img/logo.png" alt="logo"/>
|
||||
<span>OfferPie</span>
|
||||
</div>
|
||||
<div class="desc">运营管理后台</div>
|
||||
<div class="footer">© 2025 OfferPie</div>
|
||||
</div>
|
||||
<div class="right-panel">
|
||||
<div class="login-box">
|
||||
<h2>欢迎回来</h2>
|
||||
<p class="sub">请登录管理员账号</p>
|
||||
<el-form :model="form" :rules="rules" ref="formRef" @keyup.enter="handleLogin">
|
||||
<el-form-item prop="username">
|
||||
<el-input v-model="form.username" placeholder="账号" prefix-icon="User" size="large" maxlength="32"/>
|
||||
</el-form-item>
|
||||
<el-form-item prop="password">
|
||||
<el-input v-model="form.password" type="password" placeholder="密码" prefix-icon="Lock" size="large" maxlength="32" show-password/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" size="large" :loading="loading" @click="handleLogin">登录</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
|
||||
<script src="https://unpkg.com/element-plus/dist/index.full.min.js"></script>
|
||||
<script src="https://unpkg.com/@element-plus/icons-vue"></script>
|
||||
<script src="/admin/js/request.js"></script>
|
||||
<script src="/admin/js/api/auth.js"></script>
|
||||
<script>
|
||||
const { createApp, ref, reactive } = Vue;
|
||||
const app = createApp({
|
||||
setup() {
|
||||
const formRef = ref(null);
|
||||
const loading = ref(false);
|
||||
const form = reactive({ username: '', password: '' });
|
||||
const rules = {
|
||||
username: [{ required: true, message: '请输入账号', trigger: 'blur' }],
|
||||
password: [{ required: true, message: '请输入密码', trigger: 'blur' }, { min: 6, max: 32, message: '密码长度6-32位', trigger: 'blur' }]
|
||||
};
|
||||
const handleLogin = async () => {
|
||||
const valid = await formRef.value.validate().catch(() => false);
|
||||
if (!valid) return;
|
||||
loading.value = true;
|
||||
try {
|
||||
await authApi.login(form.username, form.password);
|
||||
window.location.href = '/admin/page/index';
|
||||
} catch (e) { }
|
||||
finally { loading.value = false; }
|
||||
};
|
||||
return { formRef, form, rules, loading, handleLogin };
|
||||
}
|
||||
});
|
||||
// 注册 Element Plus 图标
|
||||
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
|
||||
app.component(key, component);
|
||||
}
|
||||
app.use(ElementPlus);
|
||||
app.mount('#app');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package org.jiayunet.mapper;
|
||||
|
||||
import org.jiayunet.pojo.po.AdminUser;
|
||||
|
||||
/**
|
||||
* 管理员用户 Mapper
|
||||
*
|
||||
* @author zk
|
||||
*/
|
||||
public interface AdminUserMapper extends CommonMapper<AdminUser> {
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package org.jiayunet.pojo.po;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableLogic;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
/**
|
||||
* 管理员用户表(bg_admin_user)
|
||||
*
|
||||
* @author zk
|
||||
*/
|
||||
@Data
|
||||
@TableName("bg_admin_user")
|
||||
public class AdminUser {
|
||||
|
||||
/** 主键 */
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
|
||||
/** 登录账号 */
|
||||
private String username;
|
||||
|
||||
/** 密码(BCrypt) */
|
||||
private String password;
|
||||
|
||||
/** 真实姓名 */
|
||||
private String realName;
|
||||
|
||||
/** 昵称 */
|
||||
private String nickName;
|
||||
|
||||
/** 手机号码 */
|
||||
private String mobileNumber;
|
||||
|
||||
/** 头像URL */
|
||||
private String avatar;
|
||||
|
||||
/** 状态 0=正常 1=禁用 */
|
||||
private Integer status;
|
||||
|
||||
/** 创建时间 */
|
||||
private Instant createTime;
|
||||
|
||||
/** 更新时间 */
|
||||
private Instant updateTime;
|
||||
|
||||
/** 逻辑删除 0=正常 */
|
||||
@TableLogic(value = "0", delval = "UNIX_TIMESTAMP()")
|
||||
private Long isDelete;
|
||||
}
|
||||
Reference in New Issue
Block a user