java架构一/1:微服务电商/地基/登录
一、构建聚合工程(Java-Maven)

foodie-dev为父工程,其他为子模块。foodie-dev-api的pom.xml引入依赖-service,-service引入-mapper,-mapper引入-pojo,-pojo引入-common。

二、使用PDMan进行数据库建模,可以生成表图和表创建或版本更新等功能。



三、整合springboot
1.pom.xml:
这步难在版本,springboot,jdk,MySQL等等,有问题让AI去寻找协调的依赖即可
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0</version>
<relativePath/>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<java.version>17</java.version>
</properties>
<dependencies>
<!-- Spring Boot 基础 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<!-- MyBatis-Plus(兼容 Spring Boot 3.x) -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.5</version>
<exclusions>
<exclusion>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- MySQL 8+ 驱动 -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.2.0</version>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
<scope>provided</scope>
</dependency>
</dependencies>
2.application.yml:
# Web 访问端口号
server:
port: 8088
tomcat:
uri-encoding: UTF-8
max-http-header-size: 80KB############################################################
#
# 配置数据源信息
#
############################################################
spring:
datasource:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.64.128:3306/foodie-shop-dev?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai&autoReconnect=true&allowPublicKeyRetrieval=true
username: root
password: root
hikari:
connection-timeout: 30000 # 连接超时时间(毫秒)
minimum-idle: 5 # 最小连接数
maximum-pool-size: 20 # 最大连接数
auto-commit: true # 自动提交
idle-timeout: 600000 # 空闲连接最大存活时间(毫秒)
pool-name: DateSourceHikariCP # 连接池名字
max-lifetime: 1800000 # 连接的最大生命周期(毫秒)
connection-test-query: SELECT 1 # 连接测试查询############################################################
#
# MyBatis 配置
#
############################################################
mybatis:
type-aliases-package: com.imooc.pojo # 所有 POJO 类所在包路径
mapper-locations: classpath:mapper/*.xml # Mapper XML 文件路径############################################################
#
# MyBatis-Plus 配置
#
############################################################
mybatis-plus:
configuration:
map-underscore-to-camel-case: true # 开启驼峰命名转换
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 打印 SQL 日志
global-config:
db-config:
id-type: auto # 主键自增
logic-delete-field: deleted # 逻辑删除字段名
logic-delete-value: 1 # 逻辑删除值
logic-not-delete-value: 0 # 逻辑未删除值
mybatis-plus在mybatis上只增强不修改,所以我选择了MP,因此springboot用3,jdk17,MySQL8......此外,不论是mybatis还是MP都有相应的配置一键生成工具(另一个项目),可以自行去寻找。不过我直接用通义灵码的智能体插件,告诉它需要生成的位置,实体类的字段,需要生成的文件,也能快速生成一套基本的增删改查类(pojo,mapper,service,serviceImpl)。

引入lombok依赖,不用写实体类的get,set方法,只需要注解,很方便。
数据源url配置根据自己MySQL下载位置修改,一般是localhost,我是下载到了虚拟机Ubuntu中。数据库名(foodie-shop-dev)要与自己创建的一致。
3.启动类
package com.imooc;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
@SpringBootApplication
@ComponentScan(basePackages = {"com.imooc", "org.n3r.idworker"})
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
三、api接口
1.遵循restful编写风格
2.可以使用PostMan去调试接口,很简单,自行学习 
swgger
没使用教学教学视频中较老的swagger2,因为版本不兼容,所以改成了更新的openapi,UI增强使用的knife4j(能有更好看的界面)。生成api文档更方便前端人员去调用接口去请求。(文中未说明的,依赖都是引入到父工程的pom中)
1.pom.xml:
<!-- springdoc-openapi (替代 Swagger2,兼容 Spring Boot 3.x) -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.3.0</version>
</dependency>
<!-- knife4j 增强 UI (支持 OpenAPI 3) -->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
<version>4.4.0</version>
</dependency>
2.Swagger.java
位置:foodie-dev\foodie-dev-api\src\main\java\com\imooc\config\Swagger.java
package com.imooc.config;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Contact;
import io.swagger.v3.oas.models.info.Info;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
// http://localhost:8088/swagger-ui/index.html
// http://localhost:8088/doc.html
@Configuration
public class Swagger {
@Bean
public OpenAPI customOpenAPI() {
return new OpenAPI()
.info(new Info()
.title("饕餮吃货 电商平台接口 api")
.version("1.0.1")
.description("饕餮吃货 电商平台接口 api 文档")
.contact(new Contact()
.name("imooc")
.email("123@imooc.com")
.url("http://www.imooc.com")));
}
}
3.application.yml
############################################################
#
# Knife4j 增强配置
#
############################################################
springdoc:
api-docs:
enabled: true
path: /v3/api-docs
swagger-ui:
enabled: true
path: /swagger-ui.html
group-configs:
- group: 'default'
paths-to-match: '/**'
packages-to-scan: com.imooc.controller
# Knife4j 增强 UI 配置
knife4j:
enable: true
setting:
language: zh_cn
swagger-model-name: 实体类列表
四、跨域解决
foodie-dev\foodie-dev-api\src\main\java\com\imooc\config\CorsConfig.java
package com.imooc.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
@Configuration
public class CorsConfig {
public CorsConfig(){
}
@Bean
public CorsFilter corsFilter(){
// 1. 添加CORS配置信息
CorsConfiguration config = new CorsConfiguration();
config.addAllowedOrigin("http://localhost:8080");
// 是否发送cookie信息
config.setAllowCredentials(true);
// 允许的请求方式
config.addAllowedMethod("*");
// 允许的header
config.addAllowedHeader("*");
// 2. 为URL添加映射路径
UrlBasedCorsConfigurationSource corsSource = new UrlBasedCorsConfigurationSource();
corsSource.registerCorsConfiguration("/**", config);
return new CorsFilter(corsSource);
}
}
五、基本功能实现
1.判断用户名是否存在
1.UserServiceImpl:
主要用于前端页面的用户名校验,用户填写密码前就能校验出结果。
@Autowired
private UserMapper userMapper;
/**
* 判断用户名是否存在
* @param username
*/
@Override
public boolean queryUsernameIsExist(String username) {
if (StringUtils.isBlank(username)) {
return false;
}
LambdaQueryWrapper<Users> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(Users::getUsername, username);
Users user = this.baseMapper.selectOne(wrapper);
return user != null;
}
2.PassportController
**
* 校验用户名是否存在
*
* @param username 用户名
* @return 200-OK(用户名可用), 500-用户名已存在或为空
*/
@Operation(summary = "用户名是否存在", description = "用户名是否存在")
@GetMapping()
public IMOCCJSONResult checkUsername(
@Parameter(description = "用户名", required = true) @RequestParam String username) {
// 1. 校验用户名是否为空
if (StringUtils.isBlank(username)) {
return IMOCCJSONResult.errorMsg("用户名不能为空");
}
// 2. 校验用户名是否存在
boolean isExist = userService.queryUsernameIsExist(username);
if (isExist) {
return IMOCCJSONResult.errorMsg("用户名已存在,请换一个试试");
}
// 3. 用户名可用
return IMOCCJSONResult.ok("用户名可用");
}
2.注册功能
1.User.java
package com.imooc.pojo;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
* 用户实体类
*/
@Data
@TableName("users")
public class Users implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键 ID
*/
@TableId(value = "id", type = IdType.ASSIGN_UUID)
private String id;
/**
* 用户名
*/
private String username;
/**
* 密码
*/
private String password;
/**
* 昵称
*/
private String nickname;
/**
* 真实姓名
*/
private String realname;
/**
* 头像
*/
private String face;
/**
* 手机号
*/
private String mobile;
/**
* 邮箱
*/
private String email;
/**
* 性别 (1:男 0:女 2:保密)
*/
private Integer sex;
/**
* 生日
*/
private Date birthday;
/**
* 创建时间
*/
private Date createdTime;
/**
* 更新时间
*/
private Date updatedTime;
}
2.UserServiceImpl
/**
* 用户服务实现类
*/
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, Users> implements UserService {
@Autowired
public Sid sid;
private static final String USER_FACE = "https://img.zcool.cn/community/01a2f05e8d07e45f4ab05c1f60b4df.jpg";
@Autowired
private UserMapper userMapper;
/**
* 创建用户
* @param userBo
*/
@Override
public Users createUser(UserBo userBo) {
String userId = sid.nextShort();
Users user = new Users();
user.setId(userId);
user.setUsername(userBo.getUsername());
try {
user.setPassword(MD5Utils.getMD5Str(userBo.getPassword()));
} catch (Exception e) {
throw new RuntimeException(e);
}
user.setNickname(userBo.getUsername());
user.setFace(USER_FACE);
user.setBirthday(DateUtil.stringToDate("1900-01-01"));
user.setSex(Sex.secret.type);
user.setCreatedTime(new Date());
user.setUpdatedTime(new Date());
userMapper.insert(user);
return user;
}
这段涉及很多其他的知识点:
1.createUser(UserBo userBo),用户注册时,输入包含用户名,密码,确认密码,用于前端传入,而后端不需要对确认密码进行其他操作,因此自定义UserBo用于参数传入,且不用写过长的参数,保持优雅。
package com.imooc.pojo.bo; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; @Schema(description = "用户对象 BO") @Data public class UserBo { @Schema(description = "用户名", example = "imooc", required = true) private String username; @Schema(description = "密码", example = "123456", required = true) private String password; @Schema(description = "确认密码", example = "123456", required = false) private String confirmPassword; }
2.sid.nextShort(),使用雪花算法,来生成全局唯一id,支持分布式
3.MD5Utils.getMD5Str(userBo.getPassword()),用到了MD5加密,我们把设置的密码加密后存到数据库中显示,但实际用户登录还是用ta原先设置好的那串。
<!-- Apache 工具类 --> <dependency> <groupId>commons-codec</groupId> <artifactId>commons-codec</artifactId> <version>1.11</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.4</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-io</artifactId> <version>1.3.2</version> </dependency>
4.DateUtil.stringToDate("1900-01-01"),将字符串转换成日期。
5.user.setSex(Sex.secret.type),用枚举类设置性别,一目了然,而非数字。

3.PassportController
package com.imooc.controller;
@Tag(name = "注册登录", description = "用于用户注册登录相关接口")
@RestController
@RequestMapping("/passport")
public class PassportController {
@Autowired
private UserService userService;
/**
* 用户注册
*
* @param userBo 用户名
* @return 200-OK(用户名可用), 500-用户名已存在或为空
*/
@Operation(summary = "用户注册", description = "用户注册")
@PostMapping("/regist")
public IMOCCJSONResult regist(@RequestBody UserBo userBo,
HttpServletRequest request,
HttpServletResponse response) throws Exception {
String username = userBo.getUsername();
String password = userBo.getPassword();
String confirmPassword = userBo.getConfirmPassword();
// 0. 判断用户和密码是否为空
if (StringUtils.isBlank(username) ||
StringUtils.isBlank(password) ||
StringUtils.isBlank(confirmPassword)) {
return IMOCCJSONResult.errorMsg("用户名或密码不能为空");
}
// 1. 校验用户名是否存在
boolean isExist = userService.queryUsernameIsExist(username);
if (isExist) {
return IMOCCJSONResult.errorMsg("用户名已存在,请换一个试试");
}
// 2.密码长度不能少于 6 位
if (password.length() < 6) {
return IMOCCJSONResult.errorMsg("密码长度不能少于 6 位");
}
// 3.判断两次密码是否一致
if (!password.equals(confirmPassword)) {
return IMOCCJSONResult.errorMsg("两次密码不一致");
}
// 4. 实现注册
Users userResult = userService.createUser(userBo);
userResult = setNullProperty(userResult);
CookieUtils.setCookie(request, response, "user",
JsonUtils.objectToJson(userResult), true);
return IMOCCJSONResult.ok();
}
这有一个自定义响应数据结构类IMOCCJSONResult
package com.imooc.utils;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
/**
* 自定义响应数据结构
* 本类可提供给 H5/ios/安卓/公众号/小程序 使用
* 前端接受此类数据 (jsonobject) 后,可自行根据业务去实现相关功能
*
* 200: 表示成功
* 500: 表示错误,错误信息在 msg 字段中
* 501: bean 验证错误,不管多少个错误都以 map 形式返回
* 502: 拦截器拦截到用户 token 出错
* 555: 异常抛出信息
* 556: 用户 qg 校验异常
*/
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class IMOCCJSONResult {
/**
* 响应状态码
*/
private Integer status;
/**
* 响应消息
*/
private String msg;
/**
* 响应数据
*/
private Object data;
/**
* 响应时间戳
*/
private Long timestamp;
public IMOCCJSONResult() {
this.timestamp = System.currentTimeMillis();
}
/**
* 成功响应(不带数据)
*/
public static IMOCCJSONResult build(Integer status, String msg) {
IMOCCJSONResult result = new IMOCCJSONResult();
result.setStatus(status);
result.setMsg(msg);
return result;
}
/**
* 成功响应(带数据)
*/
public static IMOCCJSONResult build(Integer status, String msg, Object data) {
IMOCCJSONResult result = new IMOCCJSONResult();
result.setStatus(status);
result.setMsg(msg);
result.setData(data);
return result;
}
/**
* 成功响应(200)
*/
public static IMOCCJSONResult ok() {
return build(200, "OK");
}
/**
* 成功响应(200,带数据)
*/
public static IMOCCJSONResult ok(Object data) {
return build(200, "OK", data);
}
/**
* 错误响应(500)
*/
public static IMOCCJSONResult errorMsg(String msg) {
return build(500, msg);
}
/**
* 错误响应(500,带数据)
*/
public static IMOCCJSONResult errorMsg(String msg, Object data) {
return build(500, msg, data);
}
}
3.登录功能
1.UserServiceImpl
/**
* 检索用户名和密码是否匹配,用于登录
* @param username
* @param password
*/
@Override
public Users queryUserForLogin(String username, String password) {
LambdaQueryWrapper<Users> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(Users::getUsername, username);
wrapper.eq(Users::getPassword, password);
Users result = userMapper .selectOne(wrapper);
return result;
}
2.PassportController
/**
* 用户登录
*
* @param userBo 用户输入
*/
@Operation(summary = "用户登录", description = "用户登录")
@PostMapping("/login")
public IMOCCJSONResult login(@RequestBody UserBo userBo,
HttpServletRequest request,
HttpServletResponse response) throws Exception {
String username = userBo.getUsername();
String password = userBo.getPassword();
// 0. 判断用户和密码是否为空
if (StringUtils.isBlank(username) ||
StringUtils.isBlank(password)) {
return IMOCCJSONResult.errorMsg("用户名或密码不能为空");
}
// 1. 实现登录
Users userResult = userService.queryUserForLogin(username, MD5Utils.getMD5Str(password));
if (userResult == null) {
return IMOCCJSONResult.errorMsg("用户名或密码不正确");
}
// 数据敏感信息隐藏
userResult = setNullProperty(userResult);
// 保存会话
CookieUtils.setCookie(request, response, "user",
JsonUtils.objectToJson(userResult), true);
return IMOCCJSONResult.ok(userResult);
}
private Users setNullProperty(Users userResult) {
userResult.setPassword(null);
userResult.setMobile(null);
userResult.setEmail(null);
userResult.setCreatedTime(null);
userResult.setUpdatedTime(null);
return userResult;
}
注册和登录后都需要把敏感信息设置为空,然后设置cookie用于身份(令牌)识别。这部分涉及一个JsonUtils.objectToJson(userResult),在com/imooc/utils下。
<!-- Jakarta Servlet API (兼容 Spring Boot 3.x) --> <dependency> <groupId>jakarta.servlet</groupId> <artifactId>jakarta.servlet-api</artifactId> <version>6.0.0</version> <scope>provided</scope> </dependency>
4.登出
/**
* 用户退出登录
* @param userId 用户ID
*/
@Operation(summary = "用户退出登录", description = "用户退出登录")
@PostMapping("/logout")
public IMOCCJSONResult logout(@RequestParam String userId,
HttpServletRequest request,
HttpServletResponse response) {
// 清除用户相关信息的 cookie
CookieUtils.deleteCookie(request, response, "user");
// TODO 用户退出登录,需要清空购物车
// TODO 分布式会话中需要清除用户数据
return IMOCCJSONResult.ok();
}
六、日志记录
1.依赖
<!-- Spring Boot 基础 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!--引入日志依赖 抽象层 与 实现层-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.21</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.21</version>
</dependency>
2.log4j.properties
log4j.rootLogger=DEBUG,stdout,file
log4j.additivity.org.apache=true
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.threshold=INFO
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%-5p %c{1}:%L - %m%n
log4j.appender.file=org.apache.log4j.DailyRollingFileAppender
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.DatePattern='.'yyyy-MM-dd-HH-mm
log4j.appender.file.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n
log4j.appender.file.Threshold=INFO
log4j.appender.file.append=true
log4j.appender.file.File=/workspaces/logs/foodie-api/imooc.log
3.ServiceLogAspect.java
foodie-dev\foodie-dev-api\src\main\java\com\imooc\aspect\ServiceLogAspect.java
package com.imooc.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class ServiceLogAspect {
private static final Logger log = LoggerFactory.getLogger(ServiceLogAspect.class);
/**
* A0P通知:
* 1.前置通知:在方法调用之前执行
* 2.后置通知:在方法正常调用之后执行
* 3.⭐环绕通知:在方法调用之前和之后,都分别可以执行的通知
* 4.异常通知:如果在方法调用过程中发生异常,则通知
* 5.最终通知:在方法调用之后执行
*/
/**
* 切面表达式:execution代表所要执行的表达式主体
* 第一处 * 代表方法返回(所有)类型
* 第二处 包名代表aop监控的类所在的包
* 第三处 ..代表该包以及其子包下的所有类
* 第四处 *代表(所有)类名
* 第五处 *代表类中的方法名,(..)表示方法中的任何参数
*/
@Around("execution(* com.imooc.service.impl..*.*(..))")
public Object recordTimeLog(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("开始执行:{}.{}",
joinPoint.getTarget().getClass().getSimpleName(),
joinPoint.getSignature().getName());
// 记录开始时间
long begin = System.currentTimeMillis();
// 执行目标 service
Object result = joinPoint.proceed();
// 记录结束时间
long end = System.currentTimeMillis();
long takeTime = end - begin;
if (takeTime > 3000) {
log.error("执行结束,耗时{}毫秒", takeTime);
} else if (takeTime > 2000) {
log.warn("执行结束,耗时{}毫秒", takeTime);
} else {
log.info("执行结束,耗时{}毫秒", takeTime);
}
return result;
}
}
更多推荐

所有评论(0)