# 1. 流程图

image-20241022082440460

# 2. 详细设计

## 2.1 用户表结构设计

CREATE TABLE users (

  id varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,

  mobile varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '手机号',

  nickname varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '昵称',

  real_name varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '真实姓名',

  show_which_name int NOT NULL DEFAULT '2' COMMENT '对外展示名,1:真实姓名,2:昵称',

  sex int NOT NULL COMMENT '性别,1:男 0:女 2:保密',

  face varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '用户头像',

  email varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '邮箱',

  birthday date DEFAULT NULL COMMENT '生日',

  country varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '国家',

  province varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '省份',

  city varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '城市',

  district varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '区县',

  description varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '介绍',

  start_work_date date DEFAULT NULL COMMENT '我参加工作的时间',

  position varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '我当前职位/职务',

  role int NOT NULL COMMENT '身份角色,1: 求职者,2: HR。切换为HR时也可以登录求职者',

  hr_in_which_company_id varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '成为HR后,认证的(绑定的)公司主键id',

  hr_signature varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '我的一句话签名',

  hr_tags varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '我的个性化标签',

  created_time datetime NOT NULL COMMENT '创建时间',

  updated_time datetime NOT NULL COMMENT '更新时间',

  PRIMARY KEY (`id`) USING BTREE,

  UNIQUE KEY mobile (`mobile`) USING BTREE

) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户表';

## 2.2 注册云短信服务

这里使用到了腾讯云短信,为什么用腾讯云不用阿里云呢?因为腾讯云免费100条...够测试使用了

image-20241022084827814

## 2.3 配置依赖

在common包的pom中加入依赖

<dependency>

    <groupId>com.tencentcloudapi</groupId>

    <artifactId>tencentcloud-sdk-java</artifactId>

  	<!-- 可以在这里查最新的 https://central.sonatype.com/artifact/com.tencentcloudapi/tencentcloud-sdk-java -->

    <version>3.1.1129</version>

</dependency>

## 2.4 全局统一返回 R

package resp;

import common.HttpStatusEnum;

import lombok.Getter;

@Getter

public class R<T> {

    /**

     *标识返回状态

     */

    private Integer code;

    /**

     * 标识返回消息

     */

    private String message;

    /**

     * 标识返回内容

     */

    private T data;

    public R() {

    }

    public R(Integer code, T data, String message) {

        this.code = code;

        this.data = data;

        this.message = message;

    }

    /**

     * 成功返回

     */

    public static <T> R<T> ok(T data){

        return new R<>(HttpStatusEnum.SUCCESS.getCode(),data,HttpStatusEnum.SUCCESS.getMessage());

    }

    /**

     * 成功返回

     */

    public static <T> R<T> ok(T data,String message){

        return new R<>(HttpStatusEnum.SUCCESS.getCode(), data, message);

    }

    /**

     * 失败返回

     */

    public static <T> R<T> failed(HttpStatusEnum httpStatusEnum){

        return new R<>(httpStatusEnum.getCode(),null, httpStatusEnum.getMessage());

    }

    /**

     * 失败返回

     */

    public static <T> R<T> failed(String message){

        return new R<>(HttpStatusEnum.FAIL.getCode(), null, message);

    }

}

## 2.5 添加自定义异常

package exception;

import lombok.Getter;

import lombok.Setter;

/**

 * 自定义异常

 * @author xiaoqiu

 */

@Setter

@Getter

public class XiaoQiuException extends RuntimeException{

    private String code;

    public XiaoQiuException(String code, String message) {

        super(message);

        this.code = code;

    }

}

## 2.6 全局异常处理

package exception;

import lombok.extern.slf4j.Slf4j;

import org.springframework.http.HttpStatus;

import org.springframework.web.bind.annotation.ControllerAdvice;

import org.springframework.web.bind.annotation.ExceptionHandler;

import org.springframework.web.bind.annotation.ResponseBody;

import org.springframework.web.bind.annotation.ResponseStatus;

import resp.R;

/**

 * 全局异常处理器

 * @author xiaoqiu

 */

@Slf4j

@ControllerAdvice

@ResponseBody

public class GlobalExceptionHandler {

    /**

     * 处理自定义的业务异常

     */

    @ExceptionHandler(value = XiaoQiuException.class)

    public R<String> bizExceptionHandler(XiaoQiuException e) {

        log.error("发生业务异常! msg: -> ", e);

        return R.failed(e.getMessage());

    }

    /**

     * 处理空指针的异常

     */

    @ExceptionHandler(value = NullPointerException.class)

    public R<String> exceptionHandler(NullPointerException e) {

        log.error("发生空指针异常! msg: -> ", e);

        return R.failed("发生空指针异常!");

    }

    /**

     * 服务器异常

     */

    @ExceptionHandler(Exception.class)

    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)

    public R<String> exception(Exception e) {

        log.error("服务器异常! msg: -> ", e);

        return R.failed("服务器异常!");

    }

}

## 2.7 在common包配置发送短信工具类

package utils;

import com.tencentcloudapi.common.Credential;

import com.tencentcloudapi.common.exception.TencentCloudSDKException;

import com.tencentcloudapi.common.profile.ClientProfile;

import com.tencentcloudapi.common.profile.HttpProfile;

import com.tencentcloudapi.sms.v20210111.SmsClient;

import com.tencentcloudapi.sms.v20210111.models.SendSmsRequest;

import com.tencentcloudapi.sms.v20210111.models.SendSmsResponse;

import exception.XiaoQiuException;

import lombok.extern.slf4j.Slf4j;

import org.apache.commons.lang3.StringUtils;

import org.springframework.beans.factory.annotation.Value;

import org.springframework.stereotype.Component;

import static exception.ResultCode.SMS_SEND_EXCEPTION;

@Component

@Slf4j

public class SMSUtils {

    @Value("${sms.secretId:test}")

    private String secretId;

    @Value("${sms.secretKey:test}")

    private String secretKey;

    @Value("${sms.templateId:test}")

    private String templateId;

    @Value("${sms.sign.name:小秋}")

    private String signName;

    @Value("${sms.sdkAppId: 10000}")

    private String sdkAppId;

    @Value("${sms.test.switch:true}")

    private boolean smsTestSwitch;

    public void sendSMS(String phone, String code) {

        try {

            // 测试避免频发发送短信,关闭认证,直接成功

            if (smsTestSwitch) {

                return;

            }

            if (StringUtils.isBlank(phone) || StringUtils.isBlank(code)) {

                throw new XiaoQiuException(SMS_SEND_EXCEPTION, "手机号或验证码为空!");

            }

            

            // 必要步骤:CAM密匙查询获取: https://console.cloud.tencent.com/cam/capi

            SmsClient client = getSmsClient();

            // 实例化一个请求对象,每个接口都会对应一个request对象

            SendSmsRequest req = getSendSmsRequest(phone, code);

            // 返回的resp是一个SendSmsResponse的实例,与请求对象对应

            SendSmsResponse resp = client.SendSms(req);

            // 输出json格式的字符串回包

            log.info("手机号:{}, code: {}, 短信发送结果:{}", phone, code, SendSmsResponse.toJsonString(resp));

//            System.out.println(SendSmsResponse.toJsonString(resp));

        } catch (TencentCloudSDKException e) {

            log.error("发送短信失败!", e);

        }

    }

    private SmsClient getSmsClient() {

        Credential cred = new Credential(secretId, secretKey);

        // 实例化一个http选项,可选的,没有特殊需求可以跳过

        HttpProfile httpProfile = new HttpProfile();

//            httpProfile.setReqMethod("POST"); // 默认使用POST

        /* SDK会自动指定域名。通常是不需要特地指定域名的,但是如果你访问的是金融区的服务

          则必须手动指定域名,例如sms的上海金融区域名: sms.ap-shanghai-fsi.tencentcloudapi.com /

        httpProfile.setEndpoint("sms.tencentcloudapi.com");

        // 实例化一个client选项

        ClientProfile clientProfile = new ClientProfile();

        clientProfile.setHttpProfile(httpProfile);

        // 实例化要请求产品的client对象,clientProfile是可选的

        return new SmsClient(cred, "ap-nanjing", clientProfile);

    }

    private SendSmsRequest getSendSmsRequest(String phone, String code) {

        SendSmsRequest req = new SendSmsRequest();

        String[] phoneNumberSet = {"+86" + phone};//电话号码

        req.setPhoneNumberSet(phoneNumberSet);

        // 短信应用ID: 短信SdkAppId在 [短信控制台] 添加应用后生成的实际SdkAppId

        req.setSmsSdkAppId(sdkAppId);

        // 签名

        req.setSignName(signName);

        // 模板id:必须填写已审核通过的模板 ID。模板ID可登录 [短信控制台] 查看

        req.setTemplateId(templateId);

        /* 模板参数(自定义占位变量): 若无模板参数,则设置为空 */

        String[] templateParamSet1 = {code};

        req.setTemplateParamSet(templateParamSet1);

        return req;

    }

//    可以启动一个main函数测试

//    public static void main(String[] args) {

//        try {

//            new SMSUtils().sendSMS("18812348888", "8888");

//        } catch (Exception e) {

//            e.printStackTrace();

//        }

//    }

}