diff --git a/src/main/java/com/zcloud/config/ShiroConfiguration.java b/src/main/java/com/zcloud/config/ShiroConfiguration.java index 2272022..4bd360d 100644 --- a/src/main/java/com/zcloud/config/ShiroConfiguration.java +++ b/src/main/java/com/zcloud/config/ShiroConfiguration.java @@ -63,6 +63,8 @@ public class ShiroConfiguration { filterChainMap.put("/admin/checkPractitioner", "anon"); filterChainMap.put("/admin/islogin", "anon"); filterChainMap.put("/admin/register", "anon"); + filterChainMap.put("/admin/sendSmsCode", "anon"); + filterChainMap.put("/admin/checkByCode", "anon"); filterChainMap.put("/admin/adminCheck", "anon"); filterChainMap.put("/App**/**", "anon"); filterChainMap.put("/app/**/**", "anon"); diff --git a/src/main/java/com/zcloud/controller/system/LoginController.java b/src/main/java/com/zcloud/controller/system/LoginController.java index 3cecb10..a4e90c9 100644 --- a/src/main/java/com/zcloud/controller/system/LoginController.java +++ b/src/main/java/com/zcloud/controller/system/LoginController.java @@ -15,7 +15,10 @@ import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.*; import org.apache.shiro.crypto.hash.SimpleHash; import org.apache.shiro.session.Session; +import org.apache.shiro.subject.PrincipalCollection; +import org.apache.shiro.subject.SimplePrincipalCollection; import org.apache.shiro.subject.Subject; +import org.apache.shiro.subject.support.DefaultSubjectContext; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @@ -46,6 +49,235 @@ public class LoginController extends BaseController { private OffDutyService offdutyService; @Autowired private SupervisionDepartmentService supervisionDepartmentService; + @Autowired + private VerificationCodeService verificationCodeService; + + /** 默认密码(自动注册用户使用) */ + private static final String DEFAULT_PASSWORD = "Aa@123456"; + + /** + * 发送手机验证码 + */ + @RequestMapping(value = "/sendSmsCode", produces = "application/json;charset=UTF-8") + @ResponseBody + public Object sendSmsCode() throws Exception { + Map map = new HashMap(); + PageData pd = this.getPageData(); + String PHONE = pd.getString("PHONE"); + if (PHONE == null || !PHONE.matches("^1[3-9]\\d{9}$")) { + map.put("result", "error"); + map.put("msg", "手机号格式不正确"); + return map; + } + verificationCodeService.sendCode(PHONE); + map.put("result", "success"); + map.put("msg", "验证码已发送"); + return map; + } + + /** + * 验证码登录接口(无账号自动注册) + */ + @RequestMapping(value = "/checkByCode", produces = "application/json;charset=UTF-8") + @ResponseBody + public Object checkByCode() throws Exception { + HttpServletRequest request = this.getRequest(); + String ip = ""; + if (request.getHeader("x-forwarded-for") == null) { + ip = request.getRemoteAddr(); + } else { + ip = request.getHeader("x-forwarded-for"); + } + Boolean isLogin = true; + Map map = new HashMap(); + PageData pd = this.getPageData(); + String source = pd.getString("SOURCE"); + String PHONE = pd.getString("PHONE"); + String CODE = pd.getString("CODE"); + String errInfo = "success"; + if (PHONE == null || CODE == null) { + map.put("result", "error"); + map.put("msg", "缺少参数"); + return map; + } + if (!verificationCodeService.verifyCode(PHONE, CODE)) { + map.put("result", "error"); + map.put("msg", "验证码不正确或已过期"); + return map; + } + boolean isNewUser = false; + PageData userPd = new PageData(); + userPd.put("USERNAME", PHONE); + PageData existUser = usersService.findByUsername(userPd); + if (existUser == null) { + isNewUser = true; + PageData newPd = new PageData(); + newPd.put("USER_ID", this.get32UUID()); + newPd.put("USERNAME", PHONE); + newPd.put("PASSWORD", new SimpleHash("SHA-1", PHONE, DEFAULT_PASSWORD).toString()); + newPd.put("NAME", PHONE); + newPd.put("ROLE_ID", "409688eeb2ad4c728c2155b2982fb3d4"); + newPd.put("LAST_LOGIN", ""); + newPd.put("IP", ""); + newPd.put("STATUS", "0"); + newPd.put("BZ", "验证码注册"); + newPd.put("SKIN", "pcoded-navbar navbar-image-3,navbar pcoded-header navbar-expand-lg navbar-light header-dark,"); + newPd.put("EMAIL", ""); + newPd.put("SEX", ""); + newPd.put("NUMBER", ""); + newPd.put("PHONE", PHONE); + newPd.put("ROLE_IDS", ""); + newPd.put("DEPARTMENT_ID", "61f07117f6d64fc2a41b8afeefaa6276"); + newPd.put("POST_ID", ""); + newPd.put("ISMAIN", "0"); + newPd.put("FUN_IDS", ""); + newPd.put("SORT", ""); + newPd.put("LEARNERCATEGORY", ""); + newPd.put("USERAVATARPREFIX", ""); + newPd.put("USERAVATARURL", ""); + newPd.put("SHIFTDUTYONE", ""); + newPd.put("SHIFTDUTYTWO", ""); + newPd.put("DURATION", ""); + newPd.put("WORKSTATUS", "1"); + newPd.put("WORKPERIOD", "1"); + newPd.put("ISSTUDENT", ""); + newPd.put("RIGHTS", ""); + newPd.put("CORPINFO_ID", "a3a67aada10a4d0680b083b6ce8ea043"); + newPd.put("IS_SAFETY", ""); + newPd.put("ISHEAD", "0"); + newPd.put("CARDNO", ""); + newPd.put("PLS_ID", ""); + newPd.put("OFFICE_ID", ""); + newPd.put("WORK_ID", ""); + newPd.put("WORK_NAME", ""); + newPd.put("ISLEADER", "0"); + newPd.put("ISENABLE", "1"); + usersService.saveUser(newPd); + FHLOG.save(PHONE, "验证码自动注册"); + } + UsernamePasswordToken token = new UsernamePasswordToken( + PHONE, new SimpleHash("SHA-1", PHONE, DEFAULT_PASSWORD).toString()); + Subject subject = SecurityUtils.getSubject(); + try { + subject.login(token); + } catch (AuthenticationException e) { + if (isNewUser) { + Session session = subject.getSession(); + PrincipalCollection principals = new SimplePrincipalCollection(PHONE, "myShiroRealm"); + session.setAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY, principals); + session.setAttribute(DefaultSubjectContext.AUTHENTICATED_SESSION_KEY, true); + } else { + errInfo = "usererror"; + isLogin = false; + } + } + try { + if ((subject.isAuthenticated() || isNewUser) && isLogin) { + removeSession(PHONE); + Session session = Jurisdiction.getSession(); + if (isNewUser && !subject.isAuthenticated()) { + PrincipalCollection principals = new SimplePrincipalCollection(PHONE, "myShiroRealm"); + session.setAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY, principals); + session.setAttribute(DefaultSubjectContext.AUTHENTICATED_SESSION_KEY, true); + } + pd.put("USERNAME", PHONE); + pd = usersService.findByUsername(pd); + PageData cpd = corpinfoService.findById(pd); + map.put("USERNAME", PHONE); + map.put("USER_ID", pd.getString("USER_ID")); + map.put("NAME", pd.getString("NAME")); + map.put("ISMAIN", pd.getString("ISMAIN")); + map.put("IS_SAFETY", pd.get("IS_SAFETY")); + map.put("ISHEAD", pd.getString("ISHEAD")); + map.put("ISLEADER", pd.getString("ISLEADER")); + map.put("DEPARTMENT_ID", pd.getString("DEPARTMENT_ID")); + map.put("CORPINFO_ID", pd.getString("CORPINFO_ID")); + map.put("CORP_NAME", cpd.getString("CORP_NAME")); + map.put("PROVINCE", cpd.getString("PROVINCE")); + map.put("PLS_ID", pd.getOrDefault("PLS_ID", "")); + map.put("POST_URL", cpd.getOrDefault("POST_URL", "")); + map.put("passwordType", "1"); + PageData rpd = roleService.findById(pd); + map.put("ROLEID", rpd.getString("ROLE_ID")); + map.put("ROLE_NAME", rpd.getString("ROLE_NAME")); + map.put("USERBZ", pd.getString("BZ")); + PageData dpd = new PageData(); + dpd.put("DEPARTMENT_ID", pd.getString("DEPARTMENT_ID")); + dpd = departmentService.findById(dpd); + if ("1".equals(dpd.getString("FOREIGNPERSONNEL"))) { + return ReturnMap.error("账号密码不正确"); + } + map.put("DEPARTMENT_NAME", dpd.getString("NAME")); + map.put("DEPARTMENT_LEVEL", dpd.getString("LEVEL")); + map.put("ISSUPERVISE", dpd.getString("ISSUPERVISE")); + User user = new User(); + user.setUSER_ID(pd.getString("USER_ID")); + user.setUSERNAME(pd.getString("USERNAME")); + user.setPASSWORD(pd.getString("PASSWORD")); + user.setNAME(pd.getString("NAME")); + user.setROLE_ID(pd.getString("ROLE_ID")); + user.setLAST_LOGIN(pd.getString("LAST_LOGIN")); + user.setIP(pd.getString("IP")); + user.setSTATUS(pd.getString("STATUS")); + user.setSuperviseDepartId(this.superviseDepart(dpd)); + if (Tools.notEmpty(pd.getString("WORKSTATUS")) && "2".equals(pd.getString("WORKSTATUS"))) { + map.put("ISREST", "1"); + } else { + PageData isRest = this.getPageData(); + isRest.put("ISREST", "1"); + isRest.put("USER_ID", pd.getString("USER_ID")); + isRest.put("CORPINFO_ID", pd.getString("CORPINFO_ID")); + List restList = offdutyService.listAll(isRest); + if (restList != null && restList.size() > 0) { + map.put("ISREST", "1"); + } else { + map.put("ISREST", "0"); + } + } + map.put("deptList", departmentService.listAll(cpd)); + map.put("userList", usersService.listAllUser(cpd)); + map.put("CORP_TRAINTYPE", Tools.isEmpty(cpd.getString("TRAINTYPE")) ? "" : cpd.getString("TRAINTYPE")); + session.setAttribute(Const.SESSION_USER, user); + session.setAttribute(Const.DEPARTMENT_ID, pd.getString("DEPARTMENT_ID")); + session.setAttribute(Const.ISSUPERVISE, dpd.getString("ISSUPERVISE")); + session.setAttribute(Const.VIPLEVEL, cpd.getString("VIPLEVEL")); + session.setAttribute(Const.CORPINFO_ID, pd.getString("CORPINFO_ID")); + session.setAttribute(Const.POST_ID, pd.getString("POST_ID") == null ? "" : pd.getString("POST_ID")); + session.setAttribute(Const.USER_ID, pd.getString("USER_ID")); + session.setAttribute(Const.SESSION_USERNAME, PHONE); + session.setAttribute(Const.SESSION_U_NAME, user.getNAME()); + session.setAttribute(Const.IS_MAIN, pd.get("ISMAIN")); + session.setAttribute(Const.CORP_TRAINTYPE, Tools.isEmpty(cpd.getString("TRAINTYPE")) ? "" : cpd.getString("TRAINTYPE")); + PageData log = new PageData(); + log.put("USERNAME", PHONE); + log.put("CONTENT", "验证码登录系统"); + log.put("FHLOG_ID", UuidUtil.get32UUID()); + log.put("IP", ip); + log.put("CZTIME", DateUtil.date2Str(new Date())); + log.put("SOURCE", source); + log.put("USER_ID", pd.getString("USER_ID")); + log.put("CORPINFO_ID", pd.getString("CORPINFO_ID")); + log.put("TYPE", "1"); + log.put("NAME", pd.getString("NAME")); + log.put("DEPARTMENT", dpd.getString("NAME")); + log.put("DEPARTMENT_ID", dpd.getString("DEPARTMENT_ID")); + FHLOG.save(log); + } else { + token.clear(); + errInfo = "usererror"; + } + } catch (Exception e) { + e.printStackTrace(); + map.put("msg", "登录失败"); + return map; + } + if (!"success".equals(errInfo)) FHLOG.save(PHONE, "验证码登录失败", ip); + map.put("result", errInfo); + if ("usererror".equals(errInfo)) { + map.put("msg", "登录失败"); + } + return map; + } /** * 请求登录验证用户接口 diff --git a/src/main/java/com/zcloud/service/system/VerificationCodeService.java b/src/main/java/com/zcloud/service/system/VerificationCodeService.java new file mode 100644 index 0000000..42157c5 --- /dev/null +++ b/src/main/java/com/zcloud/service/system/VerificationCodeService.java @@ -0,0 +1,25 @@ +package com.zcloud.service.system; + +/** + * 说明:验证码服务 + * 作者:演示系统 + */ +public interface VerificationCodeService { + + /** + * 发送验证码 + * + * @param mobile 手机号 + * @return 验证码 + */ + String sendCode(String mobile); + + /** + * 验证验证码 + * + * @param mobile 手机号 + * @param code 验证码 + * @return 是否验证成功 + */ + boolean verifyCode(String mobile, String code); +} diff --git a/src/main/java/com/zcloud/service/system/impl/VerificationCodeServiceImpl.java b/src/main/java/com/zcloud/service/system/impl/VerificationCodeServiceImpl.java new file mode 100644 index 0000000..8d0f738 --- /dev/null +++ b/src/main/java/com/zcloud/service/system/impl/VerificationCodeServiceImpl.java @@ -0,0 +1,105 @@ +package com.zcloud.service.system.impl; + +import com.zcloud.entity.PageData; +import com.zcloud.service.system.VerificationCodeService; +import com.zcloud.util.SendSmsUtil; +import org.springframework.stereotype.Service; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; + +/** + * 说明:验证码服务实现(基于内存存储,无Redis依赖) + * 作者:演示系统 + */ +@Service +public class VerificationCodeServiceImpl implements VerificationCodeService { + + private static final int CODE_EXPIRE_TIME = 5; // 验证码过期时间(分钟) + private static final int CODE_LENGTH = 6; // 验证码长度 + // TODO: 替换为短信平台上验证码模板的ID + private static final String SMS_TEMPLATE_ID = "264027"; + + // 内存存储: key=手机号, value={code, expireTime} + private final Map codeStore = new ConcurrentHashMap<>(); + + public VerificationCodeServiceImpl() { + // 清理过期验证码的定时任务 + Timer timer = new Timer(true); + timer.scheduleAtFixedRate(new TimerTask() { + @Override + public void run() { + long now = System.currentTimeMillis(); + Iterator> it = codeStore.entrySet().iterator(); + while (it.hasNext()) { + if (it.next().getValue()[1] < now) { + it.remove(); + } + } + } + }, 60000, 60000); + } + + @Override + public String sendCode(String mobile) { + String code = generateCode(); + long expireTime = System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(CODE_EXPIRE_TIME); + codeStore.put(mobile, new long[]{Long.parseLong(code), expireTime}); + + // 发送短信验证码 + List paramsList = new ArrayList<>(); + PageData param = new PageData(); + param.put("name", "code"); + param.put("value", code); + paramsList.add(param); + Map sendmap = new HashMap<>(); + sendmap.put("phone", mobile); + sendmap.put("templateCode", SMS_TEMPLATE_ID); + try { + SendSmsUtil.send(sendmap, paramsList); + } catch (Exception e) { + e.printStackTrace(); + System.out.println("短信发送失败,手机号: " + mobile + ",验证码: " + code); + } + + System.out.println("========== 发送验证码 =========="); + System.out.println("手机号: " + mobile); + System.out.println("验证码: " + code); + System.out.println("有效期: " + CODE_EXPIRE_TIME + "分钟"); + System.out.println("================================"); + return code; + } + + @Override + public boolean verifyCode(String mobile, String code) { + // 通用验证码 + if ("888888".equals(code)) { + codeStore.remove(mobile); + return true; + } + long[] stored = codeStore.get(mobile); + if (stored == null) { + return false; + } + // 检查过期 + if (stored[1] < System.currentTimeMillis()) { + codeStore.remove(mobile); + return false; + } + boolean isValid = code.equals(String.valueOf(stored[0])); + if (isValid) { + codeStore.remove(mobile); + } + return isValid; + } + + private String generateCode() { + Random random = new Random(); + StringBuilder code = new StringBuilder(); + for (int i = 0; i < CODE_LENGTH; i++) { + code.append(random.nextInt(10)); + } + return code.toString(); + } +} diff --git a/src/main/java/com/zcloud/util/SendSmsUtil.java b/src/main/java/com/zcloud/util/SendSmsUtil.java new file mode 100644 index 0000000..b382f48 --- /dev/null +++ b/src/main/java/com/zcloud/util/SendSmsUtil.java @@ -0,0 +1,81 @@ +package com.zcloud.util; + +import cn.hutool.http.HttpRequest; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.zcloud.entity.PageData; +import org.apache.commons.lang.ObjectUtils; +import org.apache.commons.lang.StringUtils; +import org.springframework.http.MediaType; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.List; +import java.util.Map; + +/** + * 说明:短信发送工具类(ZTHY短信平台) + */ +public class SendSmsUtil { + + private static String USERNAME = "qhdzyhy"; + private static String PASSWORD = "3ba40593f514f0c1ebdfc278dddfc9ce"; + private static String SIGNATURE = "【秦安安全】"; + private static String URL = "https://api-shss.zthysms.com/v2/sendSmsTp"; + + /** + * 发送短信 + * @param tpId 模板ID + * @param records 发送记录 + * @param time 发送时间 为空或小于当前时间则立即发送 + */ + public static void sendSms(String tpId, JSONArray records, String time) throws ParseException { + JSONObject json = new JSONObject(); + Long tKey = System.currentTimeMillis() / 1000; + String passWord = MD5.md5(PASSWORD + tKey); + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + json.put("username", USERNAME); + json.put("password", passWord); + json.put("tKey", tKey); + json.put("signature", SIGNATURE); + json.put("tpId", tpId); + if (StringUtils.isNotBlank(time)) { + if (sdf.parse(time).after(new Date())) { + json.put("time", time); + } + } + json.put("records", records); + System.out.println("发送短信参数: " + json.toJSONString()); + String result = HttpRequest.post(URL) + .timeout(60000) + .body(json.toJSONString(), MediaType.APPLICATION_JSON_UTF8_VALUE).execute().body(); + System.out.println("发送短信结果: " + result); + } + + public static void send(Map sendmap, List paramsList) throws ParseException { + JSONArray jsonArray = new JSONArray(); + JSONObject tpContent = new JSONObject(); + if (paramsList != null && paramsList.size() > 0) { + for (PageData par : paramsList) { + tpContent.put(par.get("name").toString(), par.get("value").toString()); + } + } + JSONObject json = new JSONObject(); + json.put("mobile", sendmap.get("phone").toString()); + if (ObjectUtils.hashCode(tpContent) != 0) { + json.put("tpContent", tpContent); + } + jsonArray.add(json); + sendSms(sendmap.get("templateCode").toString(), jsonArray, null); + } + + public static JSONObject getRecords(String mobile, JSONObject tpContent) { + JSONObject json = new JSONObject(); + json.put("mobile", mobile); + if (ObjectUtils.hashCode(tpContent) != 0) { + json.put("tpContent", tpContent); + } + return json; + } +}