diff --git a/apps/admin/src/api/index.ts b/apps/admin/src/api/index.ts
index e16ff82..cc85475 100644
--- a/apps/admin/src/api/index.ts
+++ b/apps/admin/src/api/index.ts
@@ -39,7 +39,7 @@ export const selectChapterList = ({
chapter_course_id: string;
}) => R.post("/api/course/chapter/select", { chapter_course_id });
-export const updateChapter = (chapter: any) =>
+export const updateChapter = (chapter: IChapter) =>
R.post("/api/course/chapter/update", chapter);
export const removeCourse = (course: ICourseBasic) =>
@@ -50,3 +50,7 @@ export const createChapter = (chapterList: IChapter[]) =>
export const removeChapter = (chapter: IChapter) =>
R.post("/api/course/chapter/remove", chapter);
+
+export const selectUserList = () => R.post("/api/user/admin/select/all");
+
+export const updateUser = (user: any) => R.post("/api/user/admin/update", user);
diff --git a/apps/admin/src/view/User/index.tsx b/apps/admin/src/view/User/index.tsx
index 9487f4b..beef235 100644
--- a/apps/admin/src/view/User/index.tsx
+++ b/apps/admin/src/view/User/index.tsx
@@ -1,5 +1,192 @@
+import {
+ CheckOutlined,
+ EditOutlined,
+ SketchOutlined,
+ StopOutlined,
+ UserSwitchOutlined,
+} from "@ant-design/icons";
+import {
+ Button,
+ Card,
+ DatePicker,
+ Popconfirm,
+ Space,
+ Table,
+ Tooltip,
+} from "antd";
+import { RangePickerProps } from "antd/es/date-picker";
+import dayjs from "dayjs";
+import { useState } from "react";
+import { selectUserList, updateUser } from "../../api";
+import { useMount } from "../../hooks";
+
+interface IEditUser {
+ key: string;
+ value: any;
+}
+
+const defaultEditUser: IEditUser = {
+ key: "",
+ value: "",
+};
+
const User = () => {
- return
用户据
;
+ const [userList, setUserList] = useState([]);
+ const [editUser, setEditUser] = useState(defaultEditUser);
+
+ const onConfirmEditUser = (user: any) => {
+ updateUser(user).then((res: any) => {
+ if (res?.code === 10000) renderUserList();
+ });
+ };
+
+ const disabledDate: RangePickerProps["disabledDate"] = (current) => {
+ return current && current < dayjs().endOf("day");
+ };
+
+ const columns = [
+ {
+ title: "id",
+ dataIndex: "id",
+ key: "id",
+ },
+ {
+ title: "用户",
+ dataIndex: "user_login",
+ key: "user_login",
+ },
+ {
+ title: "创建时间",
+ dataIndex: "user_create_time",
+ key: "user_create_time",
+ render: (_: any, record: any) => {
+ return (
+
+ {dayjs(+record.user_create_time).format("YYYY-MM-DD HH:mm:ss")}
+
+ );
+ },
+ },
+ {
+ title: "状态",
+ dataIndex: "user_status",
+ key: "user_status",
+ render: (_: any, record: any) => {
+ return record.user_status ? (
+
+ ) : (
+ "-"
+ );
+ },
+ },
+ {
+ title: "订阅",
+ dataIndex: "user_sub",
+ key: "user_sub",
+ render: (_: any, record: any) => {
+ return record.user_sub ? (
+
+ ) : (
+ "-"
+ );
+ },
+ },
+ {
+ title: "订阅到期",
+ dataIndex: "user_sub_expired",
+ key: "user_sub_expired",
+ render: (_: any, record: any) => {
+ return record.user_sub ? (
+
+
+ {dayjs(+record.user_sub_expired).format("YYYY-MM-DD HH:mm:ss")}
+
+ {
+ setEditUser({
+ key: "user_sub_expired",
+ value: "" + new Date(date).getTime(),
+ });
+ }}
+ presets={[
+ { label: "订阅 - 年", value: dayjs().add(1, "year") },
+ { label: "订阅 - 季度", value: dayjs().add(3, "month") },
+ { label: "订阅 - 月", value: dayjs().add(1, "month") },
+ ]}
+ disabledDate={disabledDate}
+ format="YYYY-MM-DD HH:mm:ss"
+ showTime={{ defaultValue: dayjs("00:00:00", "HH:mm:ss") }}
+ />
+ }
+ onConfirm={() =>
+ onConfirmEditUser({ ...record, [editUser.key]: editUser.value })
+ }
+ okText="确定"
+ cancelText="取消"
+ >
+ } />
+
+
+ ) : (
+ "-"
+ );
+ },
+ },
+ {
+ title: "操作",
+ dataIndex: "operation",
+ key: "operation",
+ render: (_: any, record: any) => {
+ return (
+
+
+ }
+ onClick={() =>
+ onConfirmEditUser({
+ ...record,
+ user_status: !record.user_status,
+ })
+ }
+ />
+
+
+ }
+ onClick={() =>
+ onConfirmEditUser({ ...record, user_sub: !record.user_sub })
+ }
+ />
+
+
+ );
+ },
+ },
+ ];
+
+ const renderUserList = () => {
+ selectUserList().then((res: any) => {
+ const { data = [] } = res;
+ setUserList(data.map((u: any) => ({ ...u, key: u.id })));
+ });
+ };
+
+ useMount(() => renderUserList());
+
+ return (
+
+ );
};
export default User;
diff --git a/apps/server/src/biz/code.ts b/apps/server/src/biz/code.ts
index 9b14b13..92a517c 100644
--- a/apps/server/src/biz/code.ts
+++ b/apps/server/src/biz/code.ts
@@ -5,4 +5,5 @@ export enum BizCode {
OK = 10000,
ERROR = 20000,
AUTH = 40000,
+ FORBID = 50000,
}
diff --git a/apps/server/src/controller/course.controller.ts b/apps/server/src/controller/course.controller.ts
index 742c9c9..0572ec3 100644
--- a/apps/server/src/controller/course.controller.ts
+++ b/apps/server/src/controller/course.controller.ts
@@ -69,7 +69,9 @@ export class CourseController {
const user = await this.userService.select({ user_login });
// 用户订阅鉴权
if (!user.user_sub)
- return { code: BizCode.AUTH, msg: '无权访问订阅课程' };
+ return { code: BizCode.FORBID, msg: '无权访问订阅课程' };
+ if (+user.user_sub_expired < Date.now())
+ return { code: BizCode.FORBID, msg: '订阅已过期' };
const course = await this.courseService.select({ course_id });
const chapterList = await this.chapterService.select(course_id);
const guide = await this.guideService.select(course_id);
diff --git a/apps/server/src/controller/user.controller.ts b/apps/server/src/controller/user.controller.ts
index 1b9f642..4b412d6 100644
--- a/apps/server/src/controller/user.controller.ts
+++ b/apps/server/src/controller/user.controller.ts
@@ -10,6 +10,7 @@ import { SmsDTO } from '../dto/sms.dto';
import { RedisService } from '@midwayjs/redis';
import * as CryptoJS from 'crypto-js';
import { ADMIN, WEB } from '../config/base.config';
+import { User } from '../entity/user.entity';
@Controller('/user')
export class UserController {
@Inject()
@@ -37,6 +38,9 @@ export class UserController {
if (!verifyCode) return { code: BizCode.ERROR, msg: '验证码无效' };
// 查询用户是否存在
const userExist = await this.userService.select(params);
+ // 用户是否被封号
+ if (!userExist?.user_status)
+ return { code: BizCode.FORBID, msg: '您的账号被封禁' };
const payload = userExist?.id
? userExist
: await this.userService.createUser(params);
@@ -96,6 +100,11 @@ export class UserController {
try {
const token = this.ctx.cookies.get(WEB.SIGN);
const user = decodeToken(token);
+ const { user_login } = user;
+ // 查询用户是否被封号
+ const user_current = await this.userService.select({ user_login });
+ const { user_status } = user_current;
+ if (!user_status) return { code: BizCode.FORBID, msg: '您的账号被封禁' };
return { code: BizCode.OK, data: user };
} catch (error) {
this.ctx.logger.error(error);
@@ -107,17 +116,39 @@ export class UserController {
async verifyCode(@Body() params: SmsDTO) {
try {
const { phoneNumber: phoneNumbers, sign } = params;
+ // 查询手机号是否被封禁
+ const user = await this.userService.select({ user_login: phoneNumbers });
+ if (user && !user.user_status)
+ return { code: BizCode.FORBID, msg: '您的账号被封禁' };
+ // 防止接口调用 start
const decrypted = CryptoJS.AES.decrypt(sign, phoneNumbers);
const hackAction = decrypted.toString(CryptoJS.enc.Utf8) !== phoneNumbers;
if (hackAction) return { code: BizCode.ERROR, msg: 'fuck u' };
+ // 防止接口调用 end
const code = Math.floor(Math.random() * 9000 + 1000);
await this.redisService.set('' + phoneNumbers, code, 'EX', 60);
await this.smsService.send({ code, phoneNumbers });
return { code: BizCode.OK };
} catch (error) {
- console.log(error);
this.ctx.logger.error(error);
return { code: BizCode.ERROR, msg: '[error] /web/sms error' };
}
}
+
+ @Post('/admin/select/all')
+ async selectUser() {
+ const data = await this.userService.selectAll();
+ return { code: BizCode.OK, data };
+ }
+
+ @Post('/admin/update')
+ async updateUser(@Body() user: User) {
+ try {
+ await this.userService.update(user);
+ return { code: BizCode.OK };
+ } catch (error) {
+ this.ctx.logger.error(error);
+ return { code: BizCode.ERROR, msg: error };
+ }
+ }
}
diff --git a/apps/server/src/entity/user.entity.ts b/apps/server/src/entity/user.entity.ts
index 2f2d0fb..8437288 100644
--- a/apps/server/src/entity/user.entity.ts
+++ b/apps/server/src/entity/user.entity.ts
@@ -25,4 +25,7 @@ export class User {
@Column({ default: false })
user_sub?: boolean;
+
+ @Column()
+ user_sub_expired?: string;
}
diff --git a/apps/server/src/service/user.service.ts b/apps/server/src/service/user.service.ts
index d8b9524..43b7279 100644
--- a/apps/server/src/service/user.service.ts
+++ b/apps/server/src/service/user.service.ts
@@ -17,6 +17,10 @@ export class UserService {
return user;
}
+ async selectAll(): Promise {
+ return await this.userModel.find();
+ }
+
async createUser(user: User) {
const h = hash('' + user.user_login);
user.display_name = h.substring(0, 8);
@@ -24,4 +28,8 @@ export class UserService {
const result = await this.userModel.save(user);
return result;
}
+
+ async update(user: User) {
+ this.userModel.save(user);
+ }
}
diff --git a/apps/web/src/api/axios.ts b/apps/web/src/api/axios.ts
index eaf9aa3..d8a58e1 100644
--- a/apps/web/src/api/axios.ts
+++ b/apps/web/src/api/axios.ts
@@ -1,5 +1,7 @@
import axios from "axios";
+import Cookies from "js-cookie";
import toast from "react-hot-toast";
+import { removeLoginCookies } from "../util/cookie";
const config = {
baseURL: "",
@@ -38,6 +40,10 @@ instance.interceptors.response.use(
case 40000:
toast.error(msg);
break;
+ case 50000:
+ toast.error(msg);
+ removeLoginCookies();
+ break;
default:
// TODO ...
break;
diff --git a/apps/web/src/router/Guard.tsx b/apps/web/src/router/Guard.tsx
index 78c5b29..2ef34d2 100644
--- a/apps/web/src/router/Guard.tsx
+++ b/apps/web/src/router/Guard.tsx
@@ -1,8 +1,8 @@
import { useEffect } from "react";
import { useLocation, useNavigate } from "react-router-dom";
-import Cookies from "js-cookie";
import { useUserStore } from "../store/user.store";
import Result from "../components/Result";
+import { withLoginCookies } from "../util/cookie";
interface IGuardProps {
children: JSX.Element;
@@ -16,14 +16,13 @@ export const Guard = (props: IGuardProps) => {
const fetchUser = useUserStore((s: any) => s.fetchUser);
const location = useLocation();
- const sign = Cookies.get("_sign_web");
const needAuth = needAuthList.some((p) => location.pathname.indexOf(p) > -1);
useEffect(() => {
if (!user) fetchUser();
}, [location.pathname]);
- if (!sign && needAuth) return ;
-
+ if (!withLoginCookies() && needAuth) return ;
+
return props.children;
};
diff --git a/apps/web/src/store/user.store.ts b/apps/web/src/store/user.store.ts
index ca6d6b9..58578bf 100644
--- a/apps/web/src/store/user.store.ts
+++ b/apps/web/src/store/user.store.ts
@@ -1,15 +1,14 @@
import { create } from "zustand";
-import Cookie from "js-cookie";
import { userState } from "../api";
import toast from "react-hot-toast";
+import { removeLoginCookies, withLoginCookies } from "../util/cookie";
export const useUserStore = create((set) => {
return {
user: null,
setUser: (user: any) => set({ user }),
fetchUser: async () => {
- const sign = Cookie.get("_sign_web");
- if (!sign) return set({ user: null });
+ if (!withLoginCookies()) return set({ user: null });
userState().then((res: any) => {
const { code, data } = res;
if (code === 10000) set({ user: data });
@@ -17,8 +16,7 @@ export const useUserStore = create((set) => {
},
userExit: () => {
set({ user: null });
- Cookie.remove("_sign_web");
- Cookie.remove("_sign_web.sig");
+ removeLoginCookies();
toast.success("再见~!");
},
};
diff --git a/apps/web/src/util/cookie.ts b/apps/web/src/util/cookie.ts
new file mode 100644
index 0000000..9f7ffc4
--- /dev/null
+++ b/apps/web/src/util/cookie.ts
@@ -0,0 +1,10 @@
+import Cookie from "js-cookie";
+
+export const withLoginCookies = () => {
+ return Cookie.get("_sign_web") && Cookie.get("_sign_web.sig");
+};
+
+export const removeLoginCookies = () => {
+ Cookie.remove("_sign_web");
+ Cookie.remove("_sign_web.sig");
+};
diff --git a/apps/web/src/view/CourseDetail/index.tsx b/apps/web/src/view/CourseDetail/index.tsx
index de0caec..cd3a7bc 100644
--- a/apps/web/src/view/CourseDetail/index.tsx
+++ b/apps/web/src/view/CourseDetail/index.tsx
@@ -80,44 +80,48 @@ function CourseDetail() {
return (
- {toc.length > 0 && (
-