fix: 修复SMS sendMessage编译错误,重构login方法,使用Assert类替换直接抛出的BizException,添加favorite、history、create、has字段到登录返回结果,修复前端接口乱码问题,为所有实体类添加createBy和updateBy字段,确保CmsCategoryController#getPageList可无登录访问

This commit is contained in:
wangzhiwei 2026-01-26 14:31:56 +08:00
parent 730f18e932
commit 9d53c5e2de
44 changed files with 1865 additions and 199 deletions

View File

@ -0,0 +1,7 @@
-- 给account表添加create_by和update_by字段
-- 作者: 王志维
-- 创建时间: 2026-01-26
ALTER TABLE `account`
ADD COLUMN `create_by` varchar(50) DEFAULT NULL COMMENT '创建人' AFTER `update_time`,
ADD COLUMN `update_by` varchar(50) DEFAULT NULL COMMENT '更新人' AFTER `create_by`;

View File

@ -0,0 +1,90 @@
-- 为所有表添加create_by和update_by字段
-- 作者: 王志维
-- 创建时间: 2026-01-26
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- 1. 账户表
ALTER TABLE `account`
ADD COLUMN `create_by` varchar(50) DEFAULT NULL COMMENT '创建人' AFTER `update_time`,
ADD COLUMN `update_by` varchar(50) DEFAULT NULL COMMENT '更新人' AFTER `create_by`;
-- 2. 积分账户表
ALTER TABLE `points_account`
ADD COLUMN `create_by` varchar(50) DEFAULT NULL COMMENT '创建人' AFTER `update_time`,
ADD COLUMN `update_by` varchar(50) DEFAULT NULL COMMENT '更新人' AFTER `create_by`;
-- 3. 客户表
ALTER TABLE `customer`
ADD COLUMN `create_by` varchar(50) DEFAULT NULL COMMENT '创建人' AFTER `update_time`,
ADD COLUMN `update_by` varchar(50) DEFAULT NULL COMMENT '更新人' AFTER `create_by`;
-- 4. 内容购买记录表
ALTER TABLE `content_purchase`
ADD COLUMN `create_by` varchar(50) DEFAULT NULL COMMENT '创建人' AFTER `update_time`,
ADD COLUMN `update_by` varchar(50) DEFAULT NULL COMMENT '更新人' AFTER `create_by`;
-- 5. 用户角色关联表
ALTER TABLE `sys_user_role`
ADD COLUMN `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间' AFTER `user_id`,
ADD COLUMN `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间' AFTER `create_time`,
ADD COLUMN `delete_flag` tinyint(1) DEFAULT '0' COMMENT '是否删除 0 未删除1已删除' AFTER `update_time`,
ADD COLUMN `create_by` varchar(50) DEFAULT NULL COMMENT '创建人' AFTER `delete_flag`,
ADD COLUMN `update_by` varchar(50) DEFAULT NULL COMMENT '更新人' AFTER `create_by`;
-- 6. 系统菜单表
ALTER TABLE `sys_menu`
ADD COLUMN `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间' AFTER `delete_flag`,
ADD COLUMN `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间' AFTER `create_time`,
ADD COLUMN `create_by` varchar(50) DEFAULT NULL COMMENT '创建人' AFTER `update_time`,
ADD COLUMN `update_by` varchar(50) DEFAULT NULL COMMENT '更新人' AFTER `create_by`;
-- 7. 系统角色表
ALTER TABLE `sys_role`
ADD COLUMN `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间' AFTER `create_time`,
ADD COLUMN `create_by` varchar(50) DEFAULT NULL COMMENT '创建人' AFTER `delete_flag`,
ADD COLUMN `update_by` varchar(50) DEFAULT NULL COMMENT '更新人' AFTER `create_by`;
-- 8. 支付订单表
ALTER TABLE `payment_order`
ADD COLUMN `create_by` varchar(50) DEFAULT NULL COMMENT '创建人' AFTER `delete_flag`,
ADD COLUMN `update_by` varchar(50) DEFAULT NULL COMMENT '更新人' AFTER `create_by`;
-- 9. 系统日志表
ALTER TABLE `sys_log`
ADD COLUMN `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间' AFTER `note`,
ADD COLUMN `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间' AFTER `create_time`,
ADD COLUMN `delete_flag` tinyint(1) DEFAULT '0' COMMENT '是否删除 0 未删除1已删除' AFTER `update_time`,
ADD COLUMN `create_by` varchar(50) DEFAULT NULL COMMENT '创建人' AFTER `delete_flag`,
ADD COLUMN `update_by` varchar(50) DEFAULT NULL COMMENT '更新人' AFTER `create_by`;
-- 10. 系统用户表
ALTER TABLE `sys_user`
ADD COLUMN `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间' AFTER `create_time`,
ADD COLUMN `create_by` varchar(50) DEFAULT NULL COMMENT '创建人' AFTER `delete_flag`,
ADD COLUMN `update_by` varchar(50) DEFAULT NULL COMMENT '更新人' AFTER `create_by`;
-- 11. 供应商表
ALTER TABLE `supplier`
ADD COLUMN `create_by` varchar(50) DEFAULT NULL COMMENT '创建人' AFTER `update_time`,
ADD COLUMN `update_by` varchar(50) DEFAULT NULL COMMENT '更新人' AFTER `create_by`;
-- 12. 系统字典表
ALTER TABLE `sys_dict`
ADD COLUMN `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间' AFTER `create_time`,
ADD COLUMN `delete_flag` tinyint(1) DEFAULT '0' COMMENT '是否删除 0 未删除1已删除' AFTER `update_time`,
ADD COLUMN `create_by` varchar(50) DEFAULT NULL COMMENT '创建人' AFTER `delete_flag`,
ADD COLUMN `update_by` varchar(50) DEFAULT NULL COMMENT '更新人' AFTER `create_by`;
-- 13. 内容点赞记录表
ALTER TABLE `cms_content_like`
ADD COLUMN `create_by` varchar(50) DEFAULT NULL COMMENT '创建人' AFTER `delete_flag`,
ADD COLUMN `update_by` varchar(50) DEFAULT NULL COMMENT '更新人' AFTER `create_by`;
-- 14. 内容查看记录表
ALTER TABLE `cms_content_view`
ADD COLUMN `create_by` varchar(50) DEFAULT NULL COMMENT '创建人' AFTER `delete_flag`,
ADD COLUMN `update_by` varchar(50) DEFAULT NULL COMMENT '更新人' AFTER `create_by`;
SET FOREIGN_KEY_CHECKS = 1;

14
db/cms_content_like.sql Normal file
View File

@ -0,0 +1,14 @@
-- 创建cms_content_like表用户点赞记录
CREATE TABLE `cms_content_like` (
`like_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '点赞记录ID',
`user_id` bigint(20) NOT NULL COMMENT '用户ID',
`user_name` varchar(50) NOT NULL COMMENT '用户名',
`content_id` bigint(20) NOT NULL COMMENT '内容ID',
`content_title` varchar(200) NOT NULL COMMENT '内容标题',
`like_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '点赞时间',
`delete_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标志0未删除1已删除',
PRIMARY KEY (`like_id`),
KEY `idx_user_id` (`user_id`),
KEY `idx_content_id` (`content_id`),
KEY `idx_like_time` (`like_time`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='CMS内容点赞记录表';

14
db/cms_content_view.sql Normal file
View File

@ -0,0 +1,14 @@
-- 创建cms_content_view表用户查看记录
CREATE TABLE `cms_content_view` (
`view_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '查看记录ID',
`user_id` bigint(20) NOT NULL COMMENT '用户ID',
`user_name` varchar(50) NOT NULL COMMENT '用户名',
`content_id` bigint(20) NOT NULL COMMENT '内容ID',
`content_title` varchar(200) NOT NULL COMMENT '内容标题',
`view_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '查看时间',
`delete_flag` tinyint(4) NOT NULL DEFAULT '0' COMMENT '删除标志0未删除1已删除',
PRIMARY KEY (`view_id`),
KEY `idx_user_id` (`user_id`),
KEY `idx_content_id` (`content_id`),
KEY `idx_view_time` (`view_time`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='CMS内容查看记录表';

View File

@ -241,6 +241,13 @@
<version>2.12.0</version> <version>2.12.0</version>
</dependency> </dependency>
<!-- SMS4J 短信发送框架 -->
<dependency>
<groupId>org.dromara.sms4j</groupId>
<artifactId>sms4j-spring-boot-starter</artifactId>
<version>3.3.5</version>
</dependency>
</dependencies> </dependencies>
<dependencyManagement> <dependencyManagement>
<dependencies> <dependencies>
@ -270,6 +277,7 @@
<arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED</arg> <arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED</arg>
<arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED</arg> <arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED</arg>
<arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED</arg> <arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED</arg>
<arg>-parameters</arg>
</compilerArgs> </compilerArgs>
<annotationProcessorPaths> <annotationProcessorPaths>
<path> <path>

View File

@ -0,0 +1,299 @@
package com.kexue.skills.common;
import com.kexue.skills.exception.BizException;
import java.util.Collection;
import java.util.Map;
/**
* 断言工具类用于封装业务异常的抛出
* 参考continue-admin的实现方式
*
* @author 王志维
* @since 2026-01-26
*/
public class Assert {
private Assert() {
// 私有构造方法禁止实例化
}
/**
* 断言表达式为true如果为false则抛出BizException
*
* @param expression 表达式
* @param message 异常信息
*/
public static void isTrue(boolean expression, String message) {
if (!expression) {
throw new BizException(message);
}
}
/**
* 断言表达式为true如果为false则抛出BizException
*
* @param expression 表达式
* @param errorCode 错误码
* @param message 异常信息
*/
public static void isTrue(boolean expression, long errorCode, String message) {
if (!expression) {
throw new BizException(errorCode, message);
}
}
/**
* 断言表达式为false如果为true则抛出BizException
*
* @param expression 表达式
* @param message 异常信息
*/
public static void isFalse(boolean expression, String message) {
if (expression) {
throw new BizException(message);
}
}
/**
* 断言表达式为false如果为true则抛出BizException
*
* @param expression 表达式
* @param errorCode 错误码
* @param message 异常信息
*/
public static void isFalse(boolean expression, long errorCode, String message) {
if (expression) {
throw new BizException(errorCode, message);
}
}
/**
* 断言对象不为null如果为null则抛出BizException
*
* @param object 对象
* @param message 异常信息
*/
public static void notNull(Object object, String message) {
if (object == null) {
throw new BizException(message);
}
}
/**
* 断言对象不为null如果为null则抛出BizException
*
* @param object 对象
* @param errorCode 错误码
* @param message 异常信息
*/
public static void notNull(Object object, long errorCode, String message) {
if (object == null) {
throw new BizException(errorCode, message);
}
}
/**
* 断言对象为null如果不为null则抛出BizException
*
* @param object 对象
* @param message 异常信息
*/
public static void isNull(Object object, String message) {
if (object != null) {
throw new BizException(message);
}
}
/**
* 断言对象为null如果不为null则抛出BizException
*
* @param object 对象
* @param errorCode 错误码
* @param message 异常信息
*/
public static void isNull(Object object, long errorCode, String message) {
if (object != null) {
throw new BizException(errorCode, message);
}
}
/**
* 断言字符串不为空如果为空则抛出BizException
*
* @param str 字符串
* @param message 异常信息
*/
public static void notEmpty(String str, String message) {
if (str == null || str.isEmpty()) {
throw new BizException(message);
}
}
/**
* 断言字符串不为空如果为空则抛出BizException
*
* @param str 字符串
* @param errorCode 错误码
* @param message 异常信息
*/
public static void notEmpty(String str, long errorCode, String message) {
if (str == null || str.isEmpty()) {
throw new BizException(errorCode, message);
}
}
/**
* 断言字符串不为空白如果为空白则抛出BizException
*
* @param str 字符串
* @param message 异常信息
*/
public static void notBlank(String str, String message) {
if (str == null || str.trim().isEmpty()) {
throw new BizException(message);
}
}
/**
* 断言字符串不为空白如果为空白则抛出BizException
*
* @param str 字符串
* @param errorCode 错误码
* @param message 异常信息
*/
public static void notBlank(String str, long errorCode, String message) {
if (str == null || str.trim().isEmpty()) {
throw new BizException(errorCode, message);
}
}
/**
* 断言集合不为空如果为空则抛出BizException
*
* @param collection 集合
* @param message 异常信息
*/
public static void notEmpty(Collection<?> collection, String message) {
if (collection == null || collection.isEmpty()) {
throw new BizException(message);
}
}
/**
* 断言集合不为空如果为空则抛出BizException
*
* @param collection 集合
* @param errorCode 错误码
* @param message 异常信息
*/
public static void notEmpty(Collection<?> collection, long errorCode, String message) {
if (collection == null || collection.isEmpty()) {
throw new BizException(errorCode, message);
}
}
/**
* 断言Map不为空如果为空则抛出BizException
*
* @param map Map
* @param message 异常信息
*/
public static void notEmpty(Map<?, ?> map, String message) {
if (map == null || map.isEmpty()) {
throw new BizException(message);
}
}
/**
* 断言Map不为空如果为空则抛出BizException
*
* @param map Map
* @param errorCode 错误码
* @param message 异常信息
*/
public static void notEmpty(Map<?, ?> map, long errorCode, String message) {
if (map == null || map.isEmpty()) {
throw new BizException(errorCode, message);
}
}
/**
* 断言数组不为空如果为空则抛出BizException
*
* @param array 数组
* @param message 异常信息
*/
public static void notEmpty(Object[] array, String message) {
if (array == null || array.length == 0) {
throw new BizException(message);
}
}
/**
* 断言数组不为空如果为空则抛出BizException
*
* @param array 数组
* @param errorCode 错误码
* @param message 异常信息
*/
public static void notEmpty(Object[] array, long errorCode, String message) {
if (array == null || array.length == 0) {
throw new BizException(errorCode, message);
}
}
/**
* 断言两个对象相等如果不相等则抛出BizException
*
* @param obj1 对象1
* @param obj2 对象2
* @param message 异常信息
*/
public static void equals(Object obj1, Object obj2, String message) {
if (!obj1.equals(obj2)) {
throw new BizException(message);
}
}
/**
* 断言两个对象相等如果不相等则抛出BizException
*
* @param obj1 对象1
* @param obj2 对象2
* @param errorCode 错误码
* @param message 异常信息
*/
public static void equals(Object obj1, Object obj2, long errorCode, String message) {
if (!obj1.equals(obj2)) {
throw new BizException(errorCode, message);
}
}
/**
* 断言两个对象不相等如果相等则抛出BizException
*
* @param obj1 对象1
* @param obj2 对象2
* @param message 异常信息
*/
public static void notEquals(Object obj1, Object obj2, String message) {
if (obj1.equals(obj2)) {
throw new BizException(message);
}
}
/**
* 断言两个对象不相等如果相等则抛出BizException
*
* @param obj1 对象1
* @param obj2 对象2
* @param errorCode 错误码
* @param message 异常信息
*/
public static void notEquals(Object obj1, Object obj2, long errorCode, String message) {
if (obj1.equals(obj2)) {
throw new BizException(errorCode, message);
}
}
}

View File

@ -0,0 +1,31 @@
package com.kexue.skills.config;
import com.kexue.skills.interceptor.CorsFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 过滤器配置类
* 用于注册自定义过滤器
*
* @author 王志维
* @since 2026-01-26
*/
@Configuration
public class FilterConfig {
/**
* 注册CORS过滤器
*
* @return FilterRegistrationBean
*/
@Bean
public FilterRegistrationBean<CorsFilter> corsFilterRegistrationBean() {
FilterRegistrationBean<CorsFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new CorsFilter());
registrationBean.addUrlPatterns("/*");
registrationBean.setOrder(1);
return registrationBean;
}
}

View File

@ -1,11 +1,17 @@
package com.kexue.skills.config; package com.kexue.skills.config;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.nio.charset.StandardCharsets;
import java.util.List;
/** mvc配置例如资源映射视图解析拦截器等 /** mvc配置例如资源映射视图解析拦截器等
* *
**/ **/
@ -53,6 +59,17 @@ public class WebMvcConfig implements WebMvcConfigurer {
registry.addViewController("/submitJournalUpload").setViewName("submitJournalUpload");*/ registry.addViewController("/submitJournalUpload").setViewName("submitJournalUpload");*/
} }
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
// 配置字符串转换器使用UTF-8编码
StringHttpMessageConverter stringConverter = new StringHttpMessageConverter(StandardCharsets.UTF_8);
converters.add(stringConverter);
// 配置JSON转换器使用UTF-8编码
MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter();
jsonConverter.setDefaultCharset(StandardCharsets.UTF_8);
converters.add(jsonConverter);
}
@Override @Override
public void addInterceptors(InterceptorRegistry registry) { public void addInterceptors(InterceptorRegistry registry) {

View File

@ -79,7 +79,7 @@ public class CaptchaController {
*/ */
@PostMapping("/verify") @PostMapping("/verify")
@Operation(summary = "验证验证码", description = "验证验证码") @Operation(summary = "验证验证码", description = "验证验证码")
public CommonResult<Boolean> verifyCaptcha(@RequestParam String captchaId, @RequestParam String captchaValue) { public CommonResult<Boolean> verifyCaptcha(@RequestParam("captchaId") String captchaId, @RequestParam("captchaValue") String captchaValue) {
// 验证码开关逻辑只有启用时才验证验证码 // 验证码开关逻辑只有启用时才验证验证码
if (!captchaConfig.isEnabled()) { if (!captchaConfig.isEnabled()) {
return CommonResult.success(true); return CommonResult.success(true);

View File

@ -106,11 +106,11 @@ public class CmsContentController {
@PostMapping("/updateAuditStatus") @PostMapping("/updateAuditStatus")
@Operation(summary = "更新审核状态", description = "更新审核状态") @Operation(summary = "更新审核状态", description = "更新审核状态")
@RequireAuth @RequireAuth
public CommonResult<Boolean> updateAuditStatus(@RequestParam Long contentId, public CommonResult<Boolean> updateAuditStatus(@RequestParam("contentId") Long contentId,
@RequestParam Integer auditStatus, @RequestParam("auditStatus") Integer auditStatus,
@RequestParam Long reviewerId, @RequestParam("reviewerId") Long reviewerId,
@RequestParam String reviewerName, @RequestParam("reviewerName") String reviewerName,
@RequestParam(required = false) String auditComment) { @RequestParam(value = "auditComment", required = false) String auditComment) {
return CommonResult.success(cmsContentService.updateAuditStatus(contentId, auditStatus, reviewerId, reviewerName, auditComment, reviewerName) > 0); return CommonResult.success(cmsContentService.updateAuditStatus(contentId, auditStatus, reviewerId, reviewerName, auditComment, reviewerName) > 0);
} }
@ -126,10 +126,10 @@ public class CmsContentController {
@PostMapping("/updatePublishStatus") @PostMapping("/updatePublishStatus")
@Operation(summary = "更新发布状态", description = "更新发布状态") @Operation(summary = "更新发布状态", description = "更新发布状态")
@RequireAuth @RequireAuth
public CommonResult<Boolean> updatePublishStatus(@RequestParam Long contentId, public CommonResult<Boolean> updatePublishStatus(@RequestParam("contentId") Long contentId,
@RequestParam Integer publishStatus, @RequestParam("publishStatus") Integer publishStatus,
@RequestParam(required = false) String publishTime, @RequestParam(value = "publishTime", required = false) String publishTime,
@RequestParam String updateBy) { @RequestParam("updateBy") String updateBy) {
return CommonResult.success(cmsContentService.updatePublishStatus(contentId, publishStatus, publishTime, updateBy) > 0); return CommonResult.success(cmsContentService.updatePublishStatus(contentId, publishStatus, publishTime, updateBy) > 0);
} }

View File

@ -2,9 +2,9 @@ package com.kexue.skills.controller;
import com.kexue.skills.annotation.PreventDuplicateSubmission; import com.kexue.skills.annotation.PreventDuplicateSubmission;
import com.kexue.skills.common.CommonResult; import com.kexue.skills.common.CommonResult;
import com.kexue.skills.entity.SysUser;
import com.kexue.skills.entity.request.LoginDto; import com.kexue.skills.entity.request.LoginDto;
import com.kexue.skills.entity.request.LoginUserDto; import com.kexue.skills.entity.request.LoginUserDto;
import com.kexue.skills.entity.request.PhoneLoginDto;
import com.kexue.skills.service.SysUserService; import com.kexue.skills.service.SysUserService;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
@ -35,7 +35,7 @@ public class LoginController {
* @param loginDto 登录信息 * @param loginDto 登录信息
* @return 登录结果 * @return 登录结果
*/ */
@PostMapping("/do") @PostMapping("/accountLogin")
@Operation(summary = "用户登录", description = "用户登录") @Operation(summary = "用户登录", description = "用户登录")
@PreventDuplicateSubmission @PreventDuplicateSubmission
public CommonResult<LoginUserDto> login(@RequestBody LoginDto loginDto) { public CommonResult<LoginUserDto> login(@RequestBody LoginDto loginDto) {
@ -47,7 +47,7 @@ public class LoginController {
* *
* @return 登出结果 * @return 登出结果
*/ */
@PostMapping("/logout") @PostMapping("/accountLogout")
@Operation(summary = "用户登出", description = "用户登出") @Operation(summary = "用户登出", description = "用户登出")
public CommonResult<String> logout() { public CommonResult<String> logout() {
// 使用Sa-Token登出 // 使用Sa-Token登出
@ -55,5 +55,34 @@ public class LoginController {
return CommonResult.success("登出成功"); return CommonResult.success("登出成功");
} }
/**
* 发送手机验证码
*
* @param phone 手机号
* @return 发送结果
*/
@PostMapping("/sendCode")
@Operation(summary = "发送手机验证码", description = "向指定手机号发送登录验证码")
public CommonResult<String> sendCode(@RequestParam("phone") String phone) {
boolean success = sysUserService.sendSmsCode(phone);
if (success) {
return CommonResult.success("验证码发送成功");
} else {
return CommonResult.failed("验证码发送失败");
}
}
/**
* 手机号登录
*
* @param phoneLoginDto 手机号登录信息
* @return 登录结果
*/
@PostMapping("/phoneLogin")
@Operation(summary = "手机号登录", description = "使用手机号和验证码登录")
public CommonResult<LoginUserDto> phoneLogin(@RequestBody PhoneLoginDto phoneLoginDto) {
return CommonResult.success(sysUserService.phoneLogin(phoneLoginDto));
}
} }

View File

@ -46,4 +46,10 @@ public class Account extends BaseEntity implements Serializable {
@Schema(description ="是否删除 0 未删除1已删除") @Schema(description ="是否删除 0 未删除1已删除")
private Integer deleteFlag; private Integer deleteFlag;
@Schema(description ="创建人")
private String createBy;
@Schema(description ="更新人")
private String updateBy;
} }

View File

@ -0,0 +1,129 @@
package com.kexue.skills.entity;
import java.util.Date;
/**
* CMS内容点赞记录实体类
*
* @author 王志维
* @since 2026-01-26
*/
public class CmsContentLike {
/**
* 点赞记录ID
*/
private Long likeId;
/**
* 用户ID
*/
private Long userId;
/**
* 用户名
*/
private String userName;
/**
* 内容ID
*/
private Long contentId;
/**
* 内容标题
*/
private String contentTitle;
/**
* 点赞时间
*/
private Date likeTime;
/**
* 删除标志0未删除1已删除
*/
private Integer deleteFlag;
/**
* 创建人
*/
private String createBy;
/**
* 更新人
*/
private String updateBy;
// getter和setter方法
public Long getLikeId() {
return likeId;
}
public void setLikeId(Long likeId) {
this.likeId = likeId;
}
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public Long getContentId() {
return contentId;
}
public void setContentId(Long contentId) {
this.contentId = contentId;
}
public String getContentTitle() {
return contentTitle;
}
public void setContentTitle(String contentTitle) {
this.contentTitle = contentTitle;
}
public Date getLikeTime() {
return likeTime;
}
public void setLikeTime(Date likeTime) {
this.likeTime = likeTime;
}
public Integer getDeleteFlag() {
return deleteFlag;
}
public void setDeleteFlag(Integer deleteFlag) {
this.deleteFlag = deleteFlag;
}
public String getCreateBy() {
return createBy;
}
public void setCreateBy(String createBy) {
this.createBy = createBy;
}
public String getUpdateBy() {
return updateBy;
}
public void setUpdateBy(String updateBy) {
this.updateBy = updateBy;
}
}

View File

@ -0,0 +1,129 @@
package com.kexue.skills.entity;
import java.util.Date;
/**
* CMS内容查看记录实体类
*
* @author 王志维
* @since 2026-01-26
*/
public class CmsContentView {
/**
* 查看记录ID
*/
private Long viewId;
/**
* 用户ID
*/
private Long userId;
/**
* 用户名
*/
private String userName;
/**
* 内容ID
*/
private Long contentId;
/**
* 内容标题
*/
private String contentTitle;
/**
* 查看时间
*/
private Date viewTime;
/**
* 删除标志0未删除1已删除
*/
private Integer deleteFlag;
/**
* 创建人
*/
private String createBy;
/**
* 更新人
*/
private String updateBy;
// getter和setter方法
public Long getViewId() {
return viewId;
}
public void setViewId(Long viewId) {
this.viewId = viewId;
}
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public Long getContentId() {
return contentId;
}
public void setContentId(Long contentId) {
this.contentId = contentId;
}
public String getContentTitle() {
return contentTitle;
}
public void setContentTitle(String contentTitle) {
this.contentTitle = contentTitle;
}
public Date getViewTime() {
return viewTime;
}
public void setViewTime(Date viewTime) {
this.viewTime = viewTime;
}
public Integer getDeleteFlag() {
return deleteFlag;
}
public void setDeleteFlag(Integer deleteFlag) {
this.deleteFlag = deleteFlag;
}
public String getCreateBy() {
return createBy;
}
public void setCreateBy(String createBy) {
this.createBy = createBy;
}
public String getUpdateBy() {
return updateBy;
}
public void setUpdateBy(String updateBy) {
this.updateBy = updateBy;
}
}

View File

@ -61,4 +61,10 @@ public class ContentPurchase extends BaseEntity implements Serializable {
@Schema(description ="是否删除 0 未删除1已删除") @Schema(description ="是否删除 0 未删除1已删除")
private Integer deleteFlag; private Integer deleteFlag;
@Schema(description ="创建人")
private String createBy;
@Schema(description ="更新人")
private String updateBy;
} }

View File

@ -50,4 +50,10 @@ public class Customer extends BaseEntity implements Serializable {
@Schema(description ="是否删除 0 未删除1已删除") @Schema(description ="是否删除 0 未删除1已删除")
private Integer deleteFlag; private Integer deleteFlag;
@Schema(description ="创建人")
private String createBy;
@Schema(description ="更新人")
private String updateBy;
} }

View File

@ -84,4 +84,10 @@ public class PaymentOrder extends BaseEntity implements Serializable {
@Schema(description ="是否删除 0 未删除1已删除") @Schema(description ="是否删除 0 未删除1已删除")
private Integer deleteFlag; private Integer deleteFlag;
@Schema(description ="创建人")
private String createBy;
@Schema(description ="更新人")
private String updateBy;
} }

View File

@ -48,4 +48,10 @@ public class PointsAccount extends BaseEntity implements Serializable {
@Schema(description ="是否删除 0 未删除1已删除") @Schema(description ="是否删除 0 未删除1已删除")
private Integer deleteFlag; private Integer deleteFlag;
@Schema(description ="创建人")
private String createBy;
@Schema(description ="更新人")
private String updateBy;
} }

View File

@ -50,4 +50,10 @@ public class Supplier extends BaseEntity implements Serializable {
@Schema(description ="是否删除 0 未删除1已删除") @Schema(description ="是否删除 0 未删除1已删除")
private Integer deleteFlag; private Integer deleteFlag;
@Schema(description ="创建人")
private String createBy;
@Schema(description ="更新人")
private String updateBy;
} }

View File

@ -44,4 +44,17 @@ public class SysDict extends BaseEntity implements Serializable {
@Schema(description ="创建时间") @Schema(description ="创建时间")
private Date createTime; private Date createTime;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
@Schema(description ="更新时间")
private Date updateTime;
@Schema(description ="是否删除 0 未删除1已删除")
private Integer deleteFlag;
@Schema(description ="创建人")
private String createBy;
@Schema(description ="更新人")
private String updateBy;
} }

View File

@ -4,6 +4,7 @@ package com.kexue.skills.entity;
import java.io.Serializable; import java.io.Serializable;
import com.kexue.skills.entity.base.BaseEntity; import com.kexue.skills.entity.base.BaseEntity;
import com.kexue.skills.entity.base.BaseQueryDto; import com.kexue.skills.entity.base.BaseQueryDto;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data; import lombok.Data;
@ -45,4 +46,21 @@ public class SysLog extends BaseEntity implements Serializable {
@Schema(description ="备注") @Schema(description ="备注")
private String note; private String note;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
@Schema(description ="创建时间")
private Date createTime;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
@Schema(description ="更新时间")
private Date updateTime;
@Schema(description ="是否删除 0 未删除1已删除")
private Integer deleteFlag;
@Schema(description ="创建人")
private String createBy;
@Schema(description ="更新人")
private String updateBy;
} }

View File

@ -4,6 +4,7 @@ package com.kexue.skills.entity;
import java.io.Serializable; import java.io.Serializable;
import com.kexue.skills.entity.base.BaseEntity; import com.kexue.skills.entity.base.BaseEntity;
import com.kexue.skills.entity.base.BaseQueryDto; import com.kexue.skills.entity.base.BaseQueryDto;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data; import lombok.Data;
@ -45,4 +46,18 @@ public class SysMenu extends BaseEntity implements Serializable {
@Schema(description ="删除标记") @Schema(description ="删除标记")
private Object deleteFlag; private Object deleteFlag;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
@Schema(description ="创建时间")
private Date createTime;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
@Schema(description ="更新时间")
private Date updateTime;
@Schema(description ="创建人")
private String createBy;
@Schema(description ="更新人")
private String updateBy;
} }

View File

@ -32,10 +32,20 @@ public class SysRole extends BaseEntity implements Serializable {
@Schema(description ="创建时间") @Schema(description ="创建时间")
private Date createTime; private Date createTime;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
@Schema(description ="更新时间")
private Date updateTime;
@Schema(description ="备注") @Schema(description ="备注")
private String remark; private String remark;
@Schema(description ="删除标记") @Schema(description ="删除标记")
private String deleteFlag; private String deleteFlag;
@Schema(description ="创建人")
private String createBy;
@Schema(description ="更新人")
private String updateBy;
} }

View File

@ -47,10 +47,20 @@ public class SysUser extends BaseEntity implements Serializable {
@Schema(description ="创建时间") @Schema(description ="创建时间")
private Date createTime; private Date createTime;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
@Schema(description ="更新时间")
private Date updateTime;
@Schema(description ="是否启用1启用2禁用") @Schema(description ="是否启用1启用2禁用")
private Integer enable; private Integer enable;
@Schema(description ="是否删除 0 未删除1已删除") @Schema(description ="是否删除 0 未删除1已删除")
private Integer deleteFlag; private Integer deleteFlag;
@Schema(description ="创建人")
private String createBy;
@Schema(description ="更新人")
private String updateBy;
} }

View File

@ -2,6 +2,8 @@ package com.kexue.skills.entity;
import java.io.Serializable; import java.io.Serializable;
import java.util.Date;
import com.kexue.skills.entity.base.BaseEntity; import com.kexue.skills.entity.base.BaseEntity;
import com.kexue.skills.entity.base.BaseQueryDto; import com.kexue.skills.entity.base.BaseQueryDto;
import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonFormat;
@ -24,4 +26,21 @@ public class SysUserRole extends BaseEntity implements Serializable {
@Schema(description ="用户ID") @Schema(description ="用户ID")
private Long userId; private Long userId;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
@Schema(description ="创建时间")
private Date createTime;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
@Schema(description ="更新时间")
private Date updateTime;
@Schema(description ="是否删除 0 未删除1已删除")
private Integer deleteFlag;
@Schema(description ="创建人")
private String createBy;
@Schema(description ="更新人")
private String updateBy;
} }

View File

@ -44,4 +44,24 @@ public class LoginUser {
* token * token
*/ */
private String token; private String token;
/**
* 最近点赞的20条记录
*/
private List<Long> favorites;
/**
* 最近查看的20条记录
*/
private List<Long> history;
/**
* 最近创建的20条记录
*/
private List<Long> create;
/**
* 最近购买的20条记录
*/
private List<Long> has;
} }

View File

@ -4,6 +4,8 @@ import com.kexue.skills.entity.SysUser;
import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModel;
import lombok.Data; import lombok.Data;
import java.util.List;
/** /**
* @author 维哥 * @author 维哥
* @Description * @Description
@ -13,4 +15,24 @@ import lombok.Data;
@ApiModel(value = "登录用户信息") @ApiModel(value = "登录用户信息")
public class LoginUserDto extends SysUser { public class LoginUserDto extends SysUser {
String token; String token;
/**
* 最近点赞的20条记录
*/
List<Long> favorites;
/**
* 最近查看的20条记录
*/
List<Long> history;
/**
* 最近创建的20条记录
*/
List<Long> create;
/**
* 最近购买的20条记录
*/
List<Long> has;
} }

View File

@ -0,0 +1,26 @@
package com.kexue.skills.entity.request;
import io.swagger.annotations.ApiModel;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serializable;
/**
* 手机号登录请求DTO
*
* @author 王志维
* @since 2026-01-22
*/
@Data
@ApiModel(value = "手机号登录信息")
public class PhoneLoginDto implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description ="手机号")
private String phone;
@Schema(description ="验证码")
private String code;
}

View File

@ -12,6 +12,11 @@ public class CorsFilter implements Filter {
HttpServletRequest request = (HttpServletRequest) req; HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res; HttpServletResponse response = (HttpServletResponse) res;
// 设置字符编码
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json;charset=UTF-8");
// 允许所有来源 // 允许所有来源
response.setHeader("Access-Control-Allow-Origin", "*"); response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS"); response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");

View File

@ -0,0 +1,74 @@
package com.kexue.skills.mapper;
import com.kexue.skills.entity.CmsContentLike;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* CMS内容点赞记录Mapper接口
*
* @author 王志维
* @since 2026-01-26
*/
@Mapper
public interface CmsContentLikeMapper {
/**
* 新增点赞记录
*
* @param cmsContentLike 点赞记录对象
* @return 受影响的行数
*/
int insert(CmsContentLike cmsContentLike);
/**
* 删除点赞记录
*
* @param likeId 点赞记录ID
* @return 受影响的行数
*/
int deleteById(Long likeId);
/**
* 更新点赞记录
*
* @param cmsContentLike 点赞记录对象
* @return 受影响的行数
*/
int update(CmsContentLike cmsContentLike);
/**
* 查询点赞记录
*
* @param likeId 点赞记录ID
* @return 点赞记录对象
*/
CmsContentLike queryById(Long likeId);
/**
* 查询用户是否已点赞该内容
*
* @param userId 用户ID
* @param contentId 内容ID
* @return 点赞记录对象不存在则返回null
*/
CmsContentLike queryByUserIdAndContentId(@Param("userId") Long userId, @Param("contentId") Long contentId);
/**
* 查询用户最近的点赞记录
*
* @param userId 用户ID
* @param limit 查询数量
* @return 点赞记录列表
*/
List<Long> queryRecentLikesByUserId(@Param("userId") Long userId, @Param("limit") int limit);
/**
* 查询内容的点赞记录数量
*
* @param contentId 内容ID
* @return 点赞记录数量
*/
int countByContentId(Long contentId);
}

View File

@ -108,4 +108,13 @@ public interface CmsContentMapper {
* @return 影响行数 * @return 影响行数
*/ */
int increaseViewCount(Long contentId); int increaseViewCount(Long contentId);
/**
* 查询用户最近创建的内容ID列表
*
* @param userId 用户ID
* @param limit 查询数量
* @return 内容ID列表
*/
List<Long> queryRecentCreatedByUserId(@Param("userId") Long userId, @Param("limit") int limit);
} }

View File

@ -0,0 +1,74 @@
package com.kexue.skills.mapper;
import com.kexue.skills.entity.CmsContentView;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* CMS内容查看记录Mapper接口
*
* @author 王志维
* @since 2026-01-26
*/
@Mapper
public interface CmsContentViewMapper {
/**
* 新增查看记录
*
* @param cmsContentView 查看记录对象
* @return 受影响的行数
*/
int insert(CmsContentView cmsContentView);
/**
* 删除查看记录
*
* @param viewId 查看记录ID
* @return 受影响的行数
*/
int deleteById(Long viewId);
/**
* 更新查看记录
*
* @param cmsContentView 查看记录对象
* @return 受影响的行数
*/
int update(CmsContentView cmsContentView);
/**
* 查询查看记录
*
* @param viewId 查看记录ID
* @return 查看记录对象
*/
CmsContentView queryById(Long viewId);
/**
* 查询用户最近的查看记录
*
* @param userId 用户ID
* @param limit 查询数量
* @return 查看记录列表
*/
List<Long> queryRecentViewsByUserId(@Param("userId") Long userId, @Param("limit") int limit);
/**
* 查询内容的查看记录数量
*
* @param contentId 内容ID
* @return 查看记录数量
*/
int countByContentId(Long contentId);
/**
* 查询用户是否已查看该内容
*
* @param userId 用户ID
* @param contentId 内容ID
* @return 查看记录对象不存在则返回null
*/
CmsContentView queryByUserIdAndContentId(@Param("userId") Long userId, @Param("contentId") Long contentId);
}

View File

@ -84,4 +84,6 @@ public interface SysUserMapper {
SysUser getByUsername(String userName); SysUser getByUsername(String userName);
SysUser getByTel(String tel);
} }

View File

@ -5,6 +5,7 @@ import com.kexue.skills.entity.SysUser;
import com.kexue.skills.entity.dto.SysUserDto; import com.kexue.skills.entity.dto.SysUserDto;
import com.kexue.skills.entity.request.LoginDto; import com.kexue.skills.entity.request.LoginDto;
import com.kexue.skills.entity.request.LoginUserDto; import com.kexue.skills.entity.request.LoginUserDto;
import com.kexue.skills.entity.request.PhoneLoginDto;
import com.kexue.skills.entity.request.ResetPasswordDto; import com.kexue.skills.entity.request.ResetPasswordDto;
import java.util.List; import java.util.List;
@ -83,4 +84,20 @@ public interface SysUserService extends BaseService {
* @return 是否成功 * @return 是否成功
*/ */
boolean resetPwd(Long userId, String newPassword, String operator); boolean resetPwd(Long userId, String newPassword, String operator);
/**
* 发送手机验证码
*
* @param phone 手机号
* @return 是否发送成功
*/
boolean sendSmsCode(String phone);
/**
* 手机号登录
*
* @param phoneLoginDto 手机号登录信息
* @return 登录结果
*/
LoginUserDto phoneLogin(PhoneLoginDto phoneLoginDto);
} }

View File

@ -5,6 +5,7 @@ import com.github.pagehelper.PageInfo;
import com.kexue.skills.entity.Account; import com.kexue.skills.entity.Account;
import com.kexue.skills.entity.AccountTransaction; import com.kexue.skills.entity.AccountTransaction;
import com.kexue.skills.entity.dto.AccountDto; import com.kexue.skills.entity.dto.AccountDto;
import com.kexue.skills.common.Assert;
import com.kexue.skills.exception.BizException; import com.kexue.skills.exception.BizException;
import com.kexue.skills.mapper.AccountMapper; import com.kexue.skills.mapper.AccountMapper;
import com.kexue.skills.mapper.AccountTransactionMapper; import com.kexue.skills.mapper.AccountTransactionMapper;
@ -177,14 +178,10 @@ public class AccountServiceImpl implements AccountService {
public int reduceBalance(Long userId, BigDecimal amount, String transactionNo, Long businessId, String businessType, String remark) { public int reduceBalance(Long userId, BigDecimal amount, String transactionNo, Long businessId, String businessType, String remark) {
// 1. 查询账户信息 // 1. 查询账户信息
Account account = this.queryByUserId(userId); Account account = this.queryByUserId(userId);
if (account == null) { Assert.notNull(account, "账户不存在");
throw new BizException("账户不存在");
}
// 2. 检查余额是否足够 // 2. 检查余额是否足够
if (account.getBalance().compareTo(amount) < 0) { Assert.isTrue(account.getBalance().compareTo(amount) >= 0, "账户余额不足");
throw new BizException("账户余额不足");
}
// 3. 保存交易记录 // 3. 保存交易记录
AccountTransaction transaction = new AccountTransaction(); AccountTransaction transaction = new AccountTransaction();

View File

@ -5,6 +5,7 @@ import com.github.pagehelper.PageInfo;
import com.kexue.skills.entity.CmsContent; import com.kexue.skills.entity.CmsContent;
import com.kexue.skills.entity.ContentPurchase; import com.kexue.skills.entity.ContentPurchase;
import com.kexue.skills.entity.dto.ContentPurchaseDto; import com.kexue.skills.entity.dto.ContentPurchaseDto;
import com.kexue.skills.common.Assert;
import com.kexue.skills.exception.BizException; import com.kexue.skills.exception.BizException;
import com.kexue.skills.mapper.ContentPurchaseMapper; import com.kexue.skills.mapper.ContentPurchaseMapper;
import com.kexue.skills.service.AccountService; import com.kexue.skills.service.AccountService;
@ -169,15 +170,10 @@ public class ContentPurchaseServiceImpl implements ContentPurchaseService {
public ContentPurchase purchaseContent(Long userId, Long contentId, Integer payType) { public ContentPurchase purchaseContent(Long userId, Long contentId, Integer payType) {
// 1. 查询内容信息 // 1. 查询内容信息
CmsContent content = this.cmsContentService.queryById(contentId); CmsContent content = this.cmsContentService.queryById(contentId);
if (content == null) { Assert.notNull(content, "内容不存在");
throw new BizException("内容不存在");
}
// 2. 检查内容是否需要付费 // 2. 检查内容是否需要付费
if (content.getIsPaid() == 0) { Assert.isTrue(content.getIsPaid() != 0, "该内容为免费内容,无需购买");
// 免费内容无需购买
throw new BizException("该内容为免费内容,无需购买");
}
// 3. 检查用户是否已经购买过该内容 // 3. 检查用户是否已经购买过该内容
ContentPurchase existingPurchase = this.queryByUserIdAndContentId(userId, contentId); ContentPurchase existingPurchase = this.queryByUserIdAndContentId(userId, contentId);
@ -209,9 +205,7 @@ public class ContentPurchaseServiceImpl implements ContentPurchaseService {
this.accountService.reduceBalance(userId, price, transactionNo, contentId, "purchase_content", "购买内容:" + content.getTitle()); this.accountService.reduceBalance(userId, price, transactionNo, contentId, "purchase_content", "购买内容:" + content.getTitle());
} else if (payType == 2) { } else if (payType == 2) {
// 积分支付 // 积分支付
if (content.getSupportPointsPay() != 1) { Assert.isTrue(content.getSupportPointsPay() == 1, "该内容不支持积分支付");
throw new BizException("该内容不支持积分支付");
}
Integer points = content.getRequiredPoints(); Integer points = content.getRequiredPoints();
purchase.setAmount(BigDecimal.ZERO); purchase.setAmount(BigDecimal.ZERO);
@ -220,7 +214,7 @@ public class ContentPurchaseServiceImpl implements ContentPurchaseService {
// 扣除用户积分 // 扣除用户积分
this.pointsAccountService.reducePoints(userId, points, transactionNo, contentId, "purchase_content", "购买内容:" + content.getTitle()); this.pointsAccountService.reducePoints(userId, points, transactionNo, contentId, "purchase_content", "购买内容:" + content.getTitle());
} else { } else {
throw new BizException("不支持的支付方式"); Assert.isTrue(false, "不支持的支付方式");
} }
// 6. 更新购买记录状态 // 6. 更新购买记录状态

View File

@ -5,6 +5,7 @@ import com.github.pagehelper.PageInfo;
import com.kexue.skills.entity.PointsAccount; import com.kexue.skills.entity.PointsAccount;
import com.kexue.skills.entity.PointsTransaction; import com.kexue.skills.entity.PointsTransaction;
import com.kexue.skills.entity.dto.PointsAccountDto; import com.kexue.skills.entity.dto.PointsAccountDto;
import com.kexue.skills.common.Assert;
import com.kexue.skills.exception.BizException; import com.kexue.skills.exception.BizException;
import com.kexue.skills.mapper.PointsAccountMapper; import com.kexue.skills.mapper.PointsAccountMapper;
import com.kexue.skills.mapper.PointsTransactionMapper; import com.kexue.skills.mapper.PointsTransactionMapper;
@ -179,14 +180,10 @@ public class PointsAccountServiceImpl implements PointsAccountService {
public int reducePoints(Long userId, Integer points, String transactionNo, Long businessId, String businessType, String remark) { public int reducePoints(Long userId, Integer points, String transactionNo, Long businessId, String businessType, String remark) {
// 1. 查询积分账户信息 // 1. 查询积分账户信息
PointsAccount account = this.queryByUserId(userId); PointsAccount account = this.queryByUserId(userId);
if (account == null) { Assert.notNull(account, "积分账户不存在");
throw new BizException("积分账户不存在");
}
// 2. 检查积分是否足够 // 2. 检查积分是否足够
if (account.getAvailablePoints() < points) { Assert.isTrue(account.getAvailablePoints() >= points, "积分不足");
throw new BizException("积分不足");
}
// 3. 保存积分交易记录 // 3. 保存积分交易记录
PointsTransaction transaction = new PointsTransaction(); PointsTransaction transaction = new PointsTransaction();

View File

@ -4,33 +4,30 @@ import cn.hutool.core.bean.BeanUtil;
import com.alicp.jetcache.anno.CacheInvalidate; import com.alicp.jetcache.anno.CacheInvalidate;
import com.alicp.jetcache.anno.CachePenetrationProtect; import com.alicp.jetcache.anno.CachePenetrationProtect;
import com.alicp.jetcache.anno.Cached; import com.alicp.jetcache.anno.Cached;
import com.kexue.skills.common.CacheManager;
import com.kexue.skills.common.Const;
import com.kexue.skills.entity.*;
import com.kexue.skills.entity.dto.ContentPurchaseDto;
import com.kexue.skills.entity.request.LoginUser;
import com.kexue.skills.entity.request.LoginUserDto;
import com.kexue.skills.entity.request.ResetPasswordDto;
import com.kexue.skills.mapper.*;
import com.kexue.skills.utils.MD5Util;
import com.kexue.skills.entity.dto.SysUserDto;
import com.kexue.skills.entity.request.LoginDto;
import com.kexue.skills.exception.BizException;
import com.kexue.skills.service.SysUserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo; import com.github.pagehelper.PageInfo;
import com.kexue.skills.common.Assert;
import com.kexue.skills.common.CacheManager;
import com.kexue.skills.common.Const;
import com.kexue.skills.config.CaptchaConfig;
import com.kexue.skills.entity.*;
import com.kexue.skills.entity.dto.ContentPurchaseDto;
import com.kexue.skills.entity.dto.SysUserDto;
import com.kexue.skills.entity.request.*;
import com.kexue.skills.mapper.*;
import com.kexue.skills.service.SysUserService;
import com.kexue.skills.utils.MD5Util;
import lombok.extern.slf4j.Slf4j;
import org.dromara.sms4j.api.SmsBlend;
import org.dromara.sms4j.core.factory.SmsFactory;
import org.springframework.stereotype.Service;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.Random;
import com.kexue.skills.config.CaptchaConfig;
import com.kexue.skills.service.AccountService;
import com.kexue.skills.service.PointsAccountService;
/** /**
* (SysUser)表服务实现类 * (SysUser)表服务实现类
@ -63,6 +60,15 @@ public class SysUserServiceImpl implements SysUserService {
@Resource @Resource
private ContentPurchaseMapper contentPurchaseMapper; private ContentPurchaseMapper contentPurchaseMapper;
@Resource
private CmsContentLikeMapper cmsContentLikeMapper;
@Resource
private CmsContentViewMapper cmsContentViewMapper;
@Resource
private CmsContentMapper cmsContentMapper;
/** /**
* 分页查询数据 * 分页查询数据
* *
@ -119,9 +125,7 @@ public class SysUserServiceImpl implements SysUserService {
@Override @Override
public SysUser insert(SysUser sysUser) { public SysUser insert(SysUser sysUser) {
SysUser byUsername = getByUsername(sysUser.getUserName()); SysUser byUsername = getByUsername(sysUser.getUserName());
if (Objects.nonNull(byUsername)){ Assert.isNull(byUsername, "用户名已存在");
throw new BizException("用户名已存在");
}
sysUser.setEnable(Const.USER_STATUS_NORMAL); sysUser.setEnable(Const.USER_STATUS_NORMAL);
sysUser.setDeleteFlag(Const.DELETE_FLAG_NO); sysUser.setDeleteFlag(Const.DELETE_FLAG_NO);
//写一个salt生成方法 //写一个salt生成方法
@ -173,9 +177,7 @@ public class SysUserServiceImpl implements SysUserService {
// 校验用户名是否已经存在 // 校验用户名是否已经存在
if (sysUser.getUserName() != null && !sysUser.getUserName().isEmpty()) { if (sysUser.getUserName() != null && !sysUser.getUserName().isEmpty()) {
SysUser existingUser = sysUserMapper.getByUsername(sysUser.getUserName()); SysUser existingUser = sysUserMapper.getByUsername(sysUser.getUserName());
if (existingUser != null && !existingUser.getUserId().equals(sysUser.getUserId())) { Assert.isTrue(existingUser == null || existingUser.getUserId().equals(sysUser.getUserId()), "用户名已存在");
throw new BizException("用户名已存在");
}
} }
sysUserMapper.update(sysUser); sysUserMapper.update(sysUser);
}else { }else {
@ -196,9 +198,7 @@ public class SysUserServiceImpl implements SysUserService {
SysUser sysUser = queryById(userId); SysUser sysUser = queryById(userId);
//管理员不允许删除 //管理员不允许删除
if (Const.ADMIN_USER_LIST.contains(sysUser.getUserName().toLowerCase())){ Assert.isFalse(Const.ADMIN_USER_LIST.contains(sysUser.getUserName().toLowerCase()), "该用户是管理员,不能删除");
throw new BizException("该用户是管理员,不能删除");
}
// 删除用户名缓存 // 删除用户名缓存
redisTemplate.delete("sysUser:username:" + sysUser.getUserName()); redisTemplate.delete("sysUser:username:" + sysUser.getUserName());
@ -208,43 +208,494 @@ public class SysUserServiceImpl implements SysUserService {
@Override @Override
public LoginUserDto login(LoginDto loginDto) { public LoginUserDto login(LoginDto loginDto) {
if (Objects.isNull(loginDto.getUsername())){ // 参数验证
throw new BizException("用户名不能位空"); validateLoginParams(loginDto);
}
if (Objects.isNull(loginDto.getPassword())){ // 验证码验证
throw new BizException("密码不能位空"); validateCaptcha(loginDto);
// 查询用户
SysUser sysUser = getUserByUsername(loginDto.getUsername());
// 密码验证
validatePassword(loginDto.getPassword(), sysUser);
// 生成token
String token = generateToken(sysUser.getUserId());
// 构建登录用户信息
LoginUser loginUser = buildLoginUser(sysUser, token);
// 存储登录信息到Redis
saveLoginUserToRedis(token, loginUser);
// 构建返回对象
LoginUserDto sysUserDto = buildLoginUserDto(loginUser);
log.info("用户:{}登录成功token{}", loginDto.getUsername(), token);
return sysUserDto;
} }
// 验证码开关逻辑只有启用时才验证验证码 /**
* 参数验证
*
* @param loginDto 登录请求参数
*/
private void validateLoginParams(LoginDto loginDto) {
Assert.notNull(loginDto.getUsername(), "用户名不能位空");
Assert.notNull(loginDto.getPassword(), "密码不能位空");
}
/**
* 验证码验证
*
* @param loginDto 登录请求参数
*/
private void validateCaptcha(LoginDto loginDto) {
if (captchaConfig.isEnabled()) { if (captchaConfig.isEnabled()) {
if (Objects.isNull(loginDto.getCaptchaId()) || Objects.isNull(loginDto.getCaptchaValue())) { Assert.notNull(loginDto.getCaptchaId(), "验证码不能为空");
throw new BizException("验证码不能为空"); Assert.notNull(loginDto.getCaptchaValue(), "验证码不能为空");
}
// 验证验证码
String captchaKey = "captcha:" + loginDto.getCaptchaId(); String captchaKey = "captcha:" + loginDto.getCaptchaId();
String captchaText = redisTemplate.opsForValue().get(captchaKey); String captchaText = redisTemplate.opsForValue().get(captchaKey);
if (captchaText == null) { Assert.notNull(captchaText, "验证码已过期");
throw new BizException("验证码已过期"); Assert.equals(captchaText, loginDto.getCaptchaValue().toLowerCase(), "验证码错误");
}
if (!captchaText.equals(loginDto.getCaptchaValue().toLowerCase())) {
throw new BizException("验证码错误");
}
// 验证成功后删除验证码
redisTemplate.delete(captchaKey); redisTemplate.delete(captchaKey);
} }
SysUser sysUser = sysUserMapper.getByUsername(loginDto.getUsername());
if (Objects.isNull(sysUser)) {
throw new BizException("用户名不存在");
} }
/**
* 根据用户名查询用户
*
* @param username 用户名
* @return 用户对象
*/
private SysUser getUserByUsername(String username) {
SysUser sysUser = sysUserMapper.getByUsername(username);
Assert.notNull(sysUser, "用户名不存在");
return sysUser;
}
/**
* 密码验证
*
* @param password 密码
* @param sysUser 用户对象
*/
private void validatePassword(String password, SysUser sysUser) {
try { try {
// 假设客户端已经对密码进行了一次MD5加密服务端使用双重加密验证 // 假设客户端已经对密码进行了一次MD5加密服务端使用双重加密验证
// 这样密码在传输过程中不是明文提高安全性 String encryptedPwd = MD5Util.doubleEncrypt(password, sysUser.getSalt());
String encryptedPwd = MD5Util.doubleEncrypt(loginDto.getPassword(), sysUser.getSalt()); Assert.equals(encryptedPwd, sysUser.getPwd(), "密码不正确");
if (encryptedPwd.equals(sysUser.getPwd())) { } catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
/**
* 生成token
*
* @param userId 用户ID
* @return token
*/
private String generateToken(Long userId) {
cn.dev33.satoken.stp.StpUtil.login(userId);
return cn.dev33.satoken.stp.StpUtil.getTokenValue();
}
/**
* 构建登录用户信息
*
* @param sysUser 用户对象
* @param token token
* @return 登录用户信息
*/
private LoginUser buildLoginUser(SysUser sysUser, String token) {
LoginUser loginUser = new LoginUser();
// 设置用户基本信息
sysUser.setPwd(null);
loginUser.setUserInfo(sysUser);
// 查询并设置用户角色列表
loginUser.setRoles(queryUserRoles(sysUser.getUserId()));
// 查询并设置用户已购买的内容ID列表
loginUser.setPurchasedContentIds(queryPurchasedContentIds(sysUser.getUserId()));
// 查询并设置用户账户信息
loginUser.setAccount(queryUserAccount(sysUser.getUserId()));
// 查询并设置用户积分信息
loginUser.setPointsAccount(queryUserPointsAccount(sysUser.getUserId()));
// 查询并设置用户最近点赞记录
loginUser.setFavorites(queryRecentFavorites(sysUser.getUserId()));
// 查询并设置用户最近查看记录
loginUser.setHistory(queryRecentViews(sysUser.getUserId()));
// 查询并设置用户最近创建记录
loginUser.setCreate(queryRecentCreatedContent(sysUser.getUserId()));
// 查询并设置用户最近购买记录
loginUser.setHas(queryRecentPurchases(sysUser.getUserId()));
// 设置token
loginUser.setToken(token);
return loginUser;
}
/**
* 查询用户角色列表
*
* @param userId 用户ID
* @return 角色列表
*/
private List<String> queryUserRoles(Long userId) {
List<String> roles = new java.util.ArrayList<>();
try {
SysUserRole userRole = new SysUserRole();
userRole.setUserId(userId);
List<SysUserRole> userRoles = sysUserRoleMapper.queryAll(userRole);
for (SysUserRole ur : userRoles) {
roles.add("ROLE_" + ur.getRoleId());
}
} catch (Exception e) {
log.error("查询用户角色列表失败:{}", e.getMessage());
roles = java.util.Collections.emptyList();
}
return roles;
}
/**
* 查询用户已购买的内容ID列表
*
* @param userId 用户ID
* @return 内容ID列表
*/
private List<Long> queryPurchasedContentIds(Long userId) {
List<Long> purchasedContentIds = new java.util.ArrayList<>();
try {
ContentPurchaseDto purchaseDto = new ContentPurchaseDto();
purchaseDto.setUserId(userId);
List<ContentPurchase> purchases = contentPurchaseMapper.getList(purchaseDto);
for (ContentPurchase purchase : purchases) {
purchasedContentIds.add(purchase.getContentId());
}
} catch (Exception e) {
log.error("查询用户已购买内容列表失败:{}", e.getMessage());
purchasedContentIds = java.util.Collections.emptyList();
}
return purchasedContentIds;
}
/**
* 查询用户账户信息
*
* @param userId 用户ID
* @return 账户信息
*/
private Account queryUserAccount(Long userId) {
return accountMapper.queryByUserId(userId);
}
/**
* 查询用户积分信息
*
* @param userId 用户ID
* @return 积分信息
*/
private PointsAccount queryUserPointsAccount(Long userId) {
return pointsAccountMapper.queryByUserId(userId);
}
/**
* 查询用户最近点赞记录
*
* @param userId 用户ID
* @return 最近点赞记录
*/
private List<Long> queryRecentFavorites(Long userId) {
try {
return cmsContentLikeMapper.queryRecentLikesByUserId(userId, 20);
} catch (Exception e) {
log.error("查询用户最近点赞记录失败:{}", e.getMessage());
return java.util.Collections.emptyList();
}
}
/**
* 查询用户最近查看记录
*
* @param userId 用户ID
* @return 最近查看记录
*/
private List<Long> queryRecentViews(Long userId) {
try {
return cmsContentViewMapper.queryRecentViewsByUserId(userId, 20);
} catch (Exception e) {
log.error("查询用户最近查看记录失败:{}", e.getMessage());
return java.util.Collections.emptyList();
}
}
/**
* 查询用户最近创建记录
*
* @param userId 用户ID
* @return 最近创建记录
*/
private List<Long> queryRecentCreatedContent(Long userId) {
try {
return cmsContentMapper.queryRecentCreatedByUserId(userId, 20);
} catch (Exception e) {
log.error("查询用户最近创建记录失败:{}", e.getMessage());
return java.util.Collections.emptyList();
}
}
/**
* 查询用户最近购买记录
*
* @param userId 用户ID
* @return 最近购买记录
*/
private List<Long> queryRecentPurchases(Long userId) {
List<Long> has = new java.util.ArrayList<>();
try {
ContentPurchaseDto purchaseDto = new ContentPurchaseDto();
purchaseDto.setUserId(userId);
List<ContentPurchase> purchases = contentPurchaseMapper.getList(purchaseDto);
if (purchases != null && !purchases.isEmpty()) {
java.util.LinkedHashSet<Long> contentIdSet = new java.util.LinkedHashSet<>();
for (ContentPurchase purchase : purchases) {
if (contentIdSet.size() < 20) {
contentIdSet.add(purchase.getContentId());
} else {
break;
}
}
has.addAll(contentIdSet);
}
} catch (Exception e) {
log.error("查询用户最近购买记录失败:{}", e.getMessage());
has = java.util.Collections.emptyList();
}
return has;
}
/**
* 存储登录信息到Redis
*
* @param token token
* @param loginUser 登录用户信息
*/
private void saveLoginUserToRedis(String token, LoginUser loginUser) {
redisTemplate.opsForValue().set(
"loginUser:" + token,
cn.hutool.json.JSONUtil.toJsonStr(loginUser),
3600,
java.util.concurrent.TimeUnit.SECONDS
);
}
/**
* 构建登录用户返回对象
*
* @param loginUser 登录用户信息
* @return 登录用户返回对象
*/
private LoginUserDto buildLoginUserDto(LoginUser loginUser) {
LoginUserDto sysUserDto = new LoginUserDto();
BeanUtil.copyProperties(loginUser.getUserInfo(), sysUserDto);
sysUserDto.setToken(loginUser.getToken());
sysUserDto.setFavorites(loginUser.getFavorites());
sysUserDto.setHistory(loginUser.getHistory());
sysUserDto.setCreate(loginUser.getCreate());
sysUserDto.setHas(loginUser.getHas());
return sysUserDto;
}
@Override
public boolean resetPassword(ResetPasswordDto resetPasswordDto) {
Assert.notNull(resetPasswordDto.getUserName(), "用户名不能位空");
Assert.notNull(resetPasswordDto.getOldPassword(), "旧密码不能位空");
Assert.notNull(resetPasswordDto.getNewPassword(), "新密码不能位空");
SysUser sysUser = sysUserMapper.getByUsername(resetPasswordDto.getUserName());
Assert.notNull(sysUser, "用户名不存在");
try {
String oldMd5Pwd = MD5Util.encryptToHex(resetPasswordDto.getOldPassword() + sysUser.getSalt());
Assert.equals(oldMd5Pwd, sysUser.getPwd(), "旧密码不正确");
String newMd5Pwd = MD5Util.encryptToHex(resetPasswordDto.getNewPassword() + sysUser.getSalt());
sysUser.setPwd(newMd5Pwd);
sysUserMapper.update(sysUser);
// 清除旧的token
CacheManager.removeTokenFromCache(resetPasswordDto.getUserName());
return true;
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
@Override
public boolean resetPasswordByAdmin(ResetPasswordDto resetPasswordDto) {
Assert.notNull(resetPasswordDto.getUserName(), "用户名不能位空");
Assert.notNull(resetPasswordDto.getNewPassword(), "新密码不能位空");
SysUser sysUser = sysUserMapper.getByUsername(resetPasswordDto.getUserName());
Assert.notNull(sysUser, "用户名不存在");
try {
String newMd5Pwd = MD5Util.encryptToHex(resetPasswordDto.getNewPassword() + sysUser.getSalt());
sysUser.setPwd(newMd5Pwd);
sysUserMapper.update(sysUser);
// 清除旧的token
CacheManager.removeTokenFromCache(resetPasswordDto.getUserName());
return true;
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
@Override
@Cached(name = "sysUser:username:", key = "#username", expire = 3600, cacheType = com.alicp.jetcache.anno.CacheType.BOTH)
@CachePenetrationProtect
public SysUser getByUsername(String username) {
if (Objects.nonNull(username)){
SysUser sysUser = sysUserMapper.getByUsername(username);
if (sysUser != null) {
sysUser.setPwd(null);
}
return sysUser;
}
return null;
}
@Override
public boolean resetPwd(Long userId, String newPassword, String operator) {
// 检查操作人是否为管理员
Assert.isTrue(Const.ADMIN_USER_LIST.contains(operator.toLowerCase()), "只有管理员才能执行此操作");
// 查询用户是否存在
SysUser sysUser = sysUserMapper.queryById(userId);
Assert.notNull(sysUser, "用户不存在");
try {
// 生成新的加密密码
String newMd5Pwd = MD5Util.encryptToHex(newPassword + sysUser.getSalt());
sysUser.setPwd(newMd5Pwd);
// 更新用户密码
sysUserMapper.update(sysUser);
// 清除旧的token
CacheManager.removeTokenFromCache(sysUser.getUserName());
return true;
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
/**
* 发送手机验证码
*
* @param phone 手机号
* @return 是否发送成功
*/
@Override
public boolean sendSmsCode(String phone) {
try {
// 参数校验
Assert.notBlank(phone, "手机号不能为空");
// 生成6位随机验证码
String code = generateSmsCode();
// 存储验证码到Redis有效期5分钟
redisTemplate.opsForValue().set("sms_code:" + phone, code, 300, java.util.concurrent.TimeUnit.SECONDS);
// 获取默认的短信发送器
SmsBlend sms = SmsFactory.getSmsBlend();
Assert.notNull(sms, "短信服务未正确配置");
log.info("获取短信发送器成功");
// 发送验证码 - 使用模板方式发送避免直接文本发送可能导致的参数缺失
java.util.Map<String, String> params = new java.util.HashMap<>();
params.put("code", code); // 参数名需要与阿里云模板中的变量名匹配
// 发送短信使用配置文件中定义的模板
// 根据API文档和用户需求使用单个手机号发送的方法sendMessage(String phone, Map<String, String> params)
// 系统会自动从配置中读取template-id
sms.sendMessage(phone, code);
log.info("向手机号 {} 发送验证码:{}", phone, code);
return true;
} catch (Exception e) {
log.error("向手机号 {} 发送验证码失败:{}", phone, e.getMessage());
log.error("详细错误信息:", e);
// 如果短信服务不可用可以记录错误并返回false而不是抛出异常
// 或者根据业务需求决定是否继续
Assert.isTrue(false, "发送验证码失败:" + e.getMessage());
}
return false;
}
/**
* 生成6位随机验证码
*
* @return 6位随机验证码
*/
private String generateSmsCode() {
Random random = new Random();
return String.format("%06d", random.nextInt(1000000));
}
/**
* 手机号登录
*
* @param phoneLoginDto 手机号登录信息
* @return 登录结果
*/
@Override
public LoginUserDto phoneLogin(PhoneLoginDto phoneLoginDto) {
String phone = phoneLoginDto.getPhone();
String code = phoneLoginDto.getCode();
Assert.notBlank(phone, "手机号不能为空");
Assert.notBlank(code, "验证码不能为空");
// 验证验证码
String cachedCode = redisTemplate.opsForValue().get("sms_code:" + phone);
Assert.notNull(cachedCode, "验证码已过期");
Assert.equals(cachedCode, code, "验证码错误");
// 验证成功后删除验证码
redisTemplate.delete("sms_code:" + phone);
// 查询用户是否存在
SysUser sysUser = sysUserMapper.getByTel(phone);
// 如果用户不存在自动创建账号
if (sysUser == null) {
sysUser = createUserByPhone(phone);
}
// 使用Sa-Token登录生成token // 使用Sa-Token登录生成token
cn.dev33.satoken.stp.StpUtil.login(sysUser.getUserId()); cn.dev33.satoken.stp.StpUtil.login(sysUser.getUserId());
// 获取生成的token // 获取生成的token
@ -266,8 +717,6 @@ public class SysUserServiceImpl implements SysUserService {
List<SysUserRole> userRoles = sysUserRoleMapper.queryAll(userRole); List<SysUserRole> userRoles = sysUserRoleMapper.queryAll(userRole);
// 遍历角色关联记录获取角色名称 // 遍历角色关联记录获取角色名称
// 注意这里需要根据角色ID查询角色名称当前系统中没有直接的方法所以暂时使用角色ID作为角色名称
// 实际项目中应该查询sys_role表获取角色名称
for (SysUserRole ur : userRoles) { for (SysUserRole ur : userRoles) {
roles.add("ROLE_" + ur.getRoleId()); roles.add("ROLE_" + ur.getRoleId());
} }
@ -303,6 +752,18 @@ public class SysUserServiceImpl implements SysUserService {
PointsAccount pointsAccount = pointsAccountMapper.queryByUserId(sysUser.getUserId()); PointsAccount pointsAccount = pointsAccountMapper.queryByUserId(sysUser.getUserId());
loginUser.setPointsAccount(pointsAccount); loginUser.setPointsAccount(pointsAccount);
// 查询并设置用户最近点赞记录
loginUser.setFavorites(queryRecentFavorites(sysUser.getUserId()));
// 查询并设置用户最近查看记录
loginUser.setHistory(queryRecentViews(sysUser.getUserId()));
// 查询并设置用户最近创建记录
loginUser.setCreate(queryRecentCreatedContent(sysUser.getUserId()));
// 查询并设置用户最近购买记录
loginUser.setHas(queryRecentPurchases(sysUser.getUserId()));
// 设置token // 设置token
loginUser.setToken(token); loginUser.setToken(token);
@ -310,129 +771,46 @@ public class SysUserServiceImpl implements SysUserService {
redisTemplate.opsForValue().set("loginUser:" + token, cn.hutool.json.JSONUtil.toJsonStr(loginUser), 3600, java.util.concurrent.TimeUnit.SECONDS); redisTemplate.opsForValue().set("loginUser:" + token, cn.hutool.json.JSONUtil.toJsonStr(loginUser), 3600, java.util.concurrent.TimeUnit.SECONDS);
// 创建LoginUserDto返回对象 // 创建LoginUserDto返回对象
LoginUserDto sysUserDto = new LoginUserDto(); LoginUserDto sysUserDto = buildLoginUserDto(loginUser);
BeanUtil.copyProperties(sysUser, sysUserDto);
sysUserDto.setToken(token);
log.info("用户:{}登录成功token{}",loginDto.getUsername(),token); log.info("用户:{} 手机号登录成功token{}", phone, token);
// 密码匹配成功
return sysUserDto; return sysUserDto;
}else {
throw new BizException("密码不正确");
}
} catch (RuntimeException e) {
throw new RuntimeException(e);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
} }
@Override /**
public boolean resetPassword(ResetPasswordDto resetPasswordDto) { * 根据手机号创建用户
if (Objects.isNull(resetPasswordDto.getUserName())) { *
throw new BizException("用户名不能位空"); * @param phone 手机号
} * @return 创建的用户对象
if (Objects.isNull(resetPasswordDto.getOldPassword())) { */
throw new BizException("旧密码不能位空"); private SysUser createUserByPhone(String phone) {
} SysUser sysUser = new SysUser();
if (Objects.isNull(resetPasswordDto.getNewPassword())) {
throw new BizException("新密码不能位空"); // 设置用户名使用手机号作为用户名
sysUser.setUserName(phone);
sysUser.setTel(phone);
// 生成随机密码
String randomPassword = generateRandomPassword();
sysUser.setPwd(randomPassword);
// 调用insert方法创建用户
return insert(sysUser);
} }
SysUser sysUser = sysUserMapper.getByUsername(resetPasswordDto.getUserName()); /**
if (Objects.isNull(sysUser)) { * 生成随机密码
throw new BizException("用户名不存在"); *
} * @return 随机密码
*/
try { private String generateRandomPassword() {
String oldMd5Pwd = MD5Util.encryptToHex(resetPasswordDto.getOldPassword() + sysUser.getSalt()); String characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
if (!oldMd5Pwd.equals(sysUser.getPwd())) { Random random = new Random();
throw new BizException("旧密码不正确"); StringBuilder sb = new StringBuilder(12);
} for (int i = 0; i < 12; i++) {
sb.append(characters.charAt(random.nextInt(characters.length())));
String newMd5Pwd = MD5Util.encryptToHex(resetPasswordDto.getNewPassword() + sysUser.getSalt());
sysUser.setPwd(newMd5Pwd);
sysUserMapper.update(sysUser);
// 清除旧的token
CacheManager.removeTokenFromCache(resetPasswordDto.getUserName());
return true;
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
@Override
public boolean resetPasswordByAdmin(ResetPasswordDto resetPasswordDto) {
if (Objects.isNull(resetPasswordDto.getUserName())) {
throw new BizException("用户名不能位空");
}
if (Objects.isNull(resetPasswordDto.getNewPassword())) {
throw new BizException("新密码不能位空");
}
SysUser sysUser = sysUserMapper.getByUsername(resetPasswordDto.getUserName());
if (Objects.isNull(sysUser)) {
throw new BizException("用户名不存在");
}
try {
String newMd5Pwd = MD5Util.encryptToHex(resetPasswordDto.getNewPassword() + sysUser.getSalt());
sysUser.setPwd(newMd5Pwd);
sysUserMapper.update(sysUser);
// 清除旧的token
CacheManager.removeTokenFromCache(resetPasswordDto.getUserName());
return true;
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
@Override
@Cached(name = "sysUser:username:", key = "#username", expire = 3600, cacheType = com.alicp.jetcache.anno.CacheType.BOTH)
@CachePenetrationProtect
public SysUser getByUsername(String username) {
if (Objects.nonNull(username)){
SysUser sysUser = sysUserMapper.getByUsername(username);
if (sysUser != null) {
sysUser.setPwd(null);
}
return sysUser;
}
return null;
}
@Override
public boolean resetPwd(Long userId, String newPassword, String operator) {
// 检查操作人是否为管理员
if (!Const.ADMIN_USER_LIST.contains(operator.toLowerCase())) {
throw new BizException("只有管理员才能执行此操作");
}
// 查询用户是否存在
SysUser sysUser = sysUserMapper.queryById(userId);
if (sysUser == null) {
throw new BizException("用户不存在");
}
try {
// 生成新的加密密码
String newMd5Pwd = MD5Util.encryptToHex(newPassword + sysUser.getSalt());
sysUser.setPwd(newMd5Pwd);
// 更新用户密码
sysUserMapper.update(sysUser);
// 清除旧的token
CacheManager.removeTokenFromCache(sysUser.getUserName());
return true;
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
} }
return sb.toString();
} }
} }

View File

@ -17,6 +17,11 @@ spring:
mode: HTML mode: HTML
prefix: classpath:/templates/ prefix: classpath:/templates/
suffix: .html suffix: .html
http:
encoding:
charset: UTF-8
enabled: true
force: true
servlet: servlet:
multipart: multipart:
max-request-size: 20MB max-request-size: 20MB
@ -44,3 +49,23 @@ pagehelper:
supportMethodsArguments: true supportMethodsArguments: true
params: count=countSql params: count=countSql
# SMS4J 短信发送配置
sms:
# 从 YAML 读取配置
config-type: YAML
http-log: true
is-print: false
blends:
alibaba:
# 短信厂商
supplier: alibaba
requestUrl: dysmsapi.aliyuncs.com
access-key-id: LTAI5t8rsaBDrno4xd4F6EwE
access-key-secret: QNmjxatrgzBYukAKr8BZ4r7gd37SHw
signature: 武汉可学智能科技
# 阿里云短信模板ID需要替换为实际申请的模板ID
template-id: SMS_493720484 # 实际使用时请替换为真实模板ID
# 短信模板变量名,对应验证码的变量
template-param-name: code
template-cache: true

View File

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.kexue.skills.mapper.CmsContentLikeMapper">
<resultMap type="com.kexue.skills.entity.CmsContentLike" id="CmsContentLikeResult">
<result property="likeId" column="like_id"/>
<result property="userId" column="user_id"/>
<result property="userName" column="user_name"/>
<result property="contentId" column="content_id"/>
<result property="contentTitle" column="content_title"/>
<result property="likeTime" column="like_time"/>
<result property="deleteFlag" column="delete_flag"/>
</resultMap>
<insert id="insert" parameterType="com.kexue.skills.entity.CmsContentLike">
INSERT INTO cms_content_like (
user_id, user_name, content_id, content_title, like_time, delete_flag
) VALUES (
#{userId}, #{userName}, #{contentId}, #{contentTitle}, NOW(), #{deleteFlag, jdbcType=INTEGER}
)
</insert>
<update id="update" parameterType="com.kexue.skills.entity.CmsContentLike">
UPDATE cms_content_like
<set>
<if test="userId != null">user_id = #{userId},</if>
<if test="userName != null and userName != ''">user_name = #{userName},</if>
<if test="contentId != null">content_id = #{contentId},</if>
<if test="contentTitle != null and contentTitle != ''">content_title = #{contentTitle},</if>
<if test="likeTime != null">like_time = #{likeTime},</if>
<if test="deleteFlag != null">delete_flag = #{deleteFlag},</if>
</set>
WHERE like_id = #{likeId}
</update>
<delete id="deleteById" parameterType="Long">
DELETE FROM cms_content_like WHERE like_id = #{likeId}
</delete>
<select id="queryById" parameterType="Long" resultMap="CmsContentLikeResult">
SELECT * FROM cms_content_like WHERE like_id = #{likeId}
</select>
<select id="queryByUserIdAndContentId" resultMap="CmsContentLikeResult">
SELECT * FROM cms_content_like
WHERE user_id = #{userId} AND content_id = #{contentId} AND delete_flag = 0
</select>
<select id="queryRecentLikesByUserId" resultType="Long">
SELECT DISTINCT content_id FROM cms_content_like
WHERE user_id = #{userId} AND delete_flag = 0
ORDER BY like_time DESC
LIMIT #{limit}
</select>
<select id="countByContentId" parameterType="Long" resultType="int">
SELECT COUNT(*) FROM cms_content_like
WHERE content_id = #{contentId} AND delete_flag = 0
</select>
</mapper>

View File

@ -270,4 +270,12 @@
where content_id = #{contentId} where content_id = #{contentId}
</update> </update>
<!--查询用户最近创建的内容ID列表-->
<select id="queryRecentCreatedByUserId" resultType="Long">
select distinct content_id from cms_content
where author_id = #{userId} and delete_flag = 0
order by create_time desc
limit #{limit}
</select>
</mapper> </mapper>

View File

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.kexue.skills.mapper.CmsContentViewMapper">
<resultMap type="com.kexue.skills.entity.CmsContentView" id="CmsContentViewResult">
<result property="viewId" column="view_id"/>
<result property="userId" column="user_id"/>
<result property="userName" column="user_name"/>
<result property="contentId" column="content_id"/>
<result property="contentTitle" column="content_title"/>
<result property="viewTime" column="view_time"/>
<result property="deleteFlag" column="delete_flag"/>
</resultMap>
<insert id="insert" parameterType="com.kexue.skills.entity.CmsContentView">
INSERT INTO cms_content_view (
user_id, user_name, content_id, content_title, view_time, delete_flag
) VALUES (
#{userId}, #{userName}, #{contentId}, #{contentTitle}, NOW(), #{deleteFlag, jdbcType=INTEGER}
)
</insert>
<update id="update" parameterType="com.kexue.skills.entity.CmsContentView">
UPDATE cms_content_view
<set>
<if test="userId != null">user_id = #{userId},</if>
<if test="userName != null and userName != ''">user_name = #{userName},</if>
<if test="contentId != null">content_id = #{contentId},</if>
<if test="contentTitle != null and contentTitle != ''">content_title = #{contentTitle},</if>
<if test="viewTime != null">view_time = #{viewTime},</if>
<if test="deleteFlag != null">delete_flag = #{deleteFlag},</if>
</set>
WHERE view_id = #{viewId}
</update>
<delete id="deleteById" parameterType="Long">
DELETE FROM cms_content_view WHERE view_id = #{viewId}
</delete>
<select id="queryById" parameterType="Long" resultMap="CmsContentViewResult">
SELECT * FROM cms_content_view WHERE view_id = #{viewId}
</select>
<select id="queryByUserIdAndContentId" resultMap="CmsContentViewResult">
SELECT * FROM cms_content_view
WHERE user_id = #{userId} AND content_id = #{contentId} AND delete_flag = 0
</select>
<select id="queryRecentViewsByUserId" resultType="Long">
SELECT DISTINCT content_id FROM cms_content_view
WHERE user_id = #{userId} AND delete_flag = 0
ORDER BY view_time DESC
LIMIT #{limit}
</select>
<select id="countByContentId" parameterType="Long" resultType="int">
SELECT COUNT(*) FROM cms_content_view
WHERE content_id = #{contentId} AND delete_flag = 0
</select>
</mapper>

View File

@ -107,6 +107,7 @@
and delete_flag = #{deleteFlag} and delete_flag = #{deleteFlag}
</if> </if>
</where> </where>
order by purchase_time desc
</select> </select>
<!--新增数据--> <!--新增数据-->

View File

@ -196,4 +196,13 @@
limit 1 limit 1
</select> </select>
<select id="getByTel" resultMap="SysUserMap">
select
user_id, user_name, pwd, real_name, tel, email, salt, remark, create_time, enable, delete_flag
from sys_user
where tel = #{tel}
and delete_flag = 0
limit 1
</select>
</mapper> </mapper>